diff --git a/src/resources/filters/customnodes/callout.lua b/src/resources/filters/customnodes/callout.lua
index a8b9137f17..e109802ced 100644
--- a/src/resources/filters/customnodes/callout.lua
+++ b/src/resources/filters/customnodes/callout.lua
@@ -1,118 +1,466 @@
-- callout.lua
-- Copyright (C) 2021-2022 Posit Software, PBC
-local constants = require("modules/constants")
-
-function calloutType(div)
- for _, class in ipairs(div.attr.classes) do
- if isCallout(class) then
- local type = class:match("^callout%-(.*)")
- if type == nil then
- type = "none"
+function _callout_main()
+ local calloutidx = 1
+
+ local function calloutType(div)
+ for _, class in ipairs(div.attr.classes) do
+ if _quarto.modules.classpredicates.isCallout(class) then
+ local type = class:match("^callout%-(.*)")
+ if type == nil then
+ type = "none"
+ end
+ return type
end
- return type
end
+ return nil
end
- return nil
-end
-
-_quarto.ast.add_handler({
- -- use either string or array of strings
- class_name = { "callout", "callout-note", "callout-warning", "callout-important", "callout-caution", "callout-tip" },
- -- the name of the ast node, used as a key in extended ast filter tables
- ast_name = "Callout",
+ local function nameForCalloutStyle(calloutType)
+ if calloutType == nil then
+ return "default"
+ else
+ local name = pandoc.utils.stringify(calloutType)
+
+ if name:lower() == "minimal" then
+ return "minimal"
+ elseif name:lower() == "simple" then
+ return "simple"
+ else
+ return "default"
+ end
+ end
+ end
- -- callouts will be rendered as blocks
- kind = "Block",
+ -- an HTML callout div
+ local function calloutDiv(node)
+ node = _quarto.modules.callouts.decorate_callout_title_with_crossref(node)
- -- a function that takes the div node as supplied in user markdown
- -- and returns the custom node
- parse = function(div)
- quarto_global_state.hasCallouts = true
- local title = markdownToInlines(div.attr.attributes["title"])
- if not title or #title == 0 then
- title = resolveHeadingCaption(div)
+ -- the first heading is the title
+ local div = pandoc.Div({})
+ local c = quarto.utils.as_blocks(node.content)
+ if pandoc.utils.type(c) == "Blocks" then
+ div.content:extend(c)
+ else
+ div.content:insert(c)
end
- local old_attr = div.attr
- local appearanceRaw = div.attr.attributes["appearance"]
- local icon = div.attr.attributes["icon"]
- local collapse = div.attr.attributes["collapse"]
- div.attr.attributes["appearance"] = nil
- div.attr.attributes["collapse"] = nil
- div.attr.attributes["icon"] = nil
- local callout_type = calloutType(div)
- div.attr.classes = div.attr.classes:filter(function(class) return not isCallout(class) end)
- return quarto.Callout({
- appearance = appearanceRaw,
- title = title,
- collapse = collapse,
- content = div.content,
- icon = icon,
- type = callout_type,
- attr = old_attr,
+ local title = quarto.utils.as_inlines(node.title)
+ local callout_type = node.type
+ local calloutAppearance = node.appearance
+ local icon = node.icon
+ local collapse = node.collapse
+ local found = false
+
+ _quarto.ast.walk(title, {
+ RawInline = function(_)
+ found = true
+ end,
+ RawBlock = function(_)
+ found = true
+ end
})
- end,
-
- -- These fields will be stored in the extended ast node
- -- and available in the object passed to the custom filters
- -- They must store Pandoc AST data. "Inline" custom nodes
- -- can store Inlines in these fields, "Block" custom nodes
- -- can store Blocks (and hence also Inlines implicitly).
- slots = { "title", "content" },
-
- constructor = function(tbl)
- quarto_global_state.hasCallouts = true
-
- local t = tbl.type
- local iconDefault = true
- local appearanceDefault = nil
- if t == "none" then
- iconDefault = false
- appearanceDefault = "simple"
+
+ if calloutAppearance == _quarto.modules.constants.kCalloutAppearanceDefault and pandoc.utils.stringify(title) == "" and not found then
+ title = quarto.utils.as_inlines(pandoc.Plain(_quarto.modules.callouts.displayName(node.type)))
end
- local appearanceRaw = tbl.appearance
- if appearanceRaw == nil then
- appearanceRaw = option("callout-appearance", appearanceDefault)
+
+ -- Make an outer card div and transfer classes and id
+ local calloutDiv = pandoc.Div({})
+ calloutDiv.attr = node.attr:clone()
+
+ local identifier = node.attr.identifier
+ if identifier ~= "" then
+ node.attr.identifier = ""
+ calloutDiv.attr.identifier = identifier
+ -- inject an anchor so callouts can be linked to
+ -- local attr = pandoc.Attr(identifier, {}, {})
+ -- local anchor = pandoc.Link({}, "", "", attr)
+ -- title:insert(1, anchor)
end
- local icon = tbl.icon
- if icon == nil then
- icon = option("callout-icon", iconDefault)
- elseif icon == "false" then
- icon = false
+ div.attr.classes = pandoc.List()
+ div.attr.classes:insert("callout-body-container")
+
+ -- add card attribute
+ calloutDiv.attr.classes:insert("callout")
+ calloutDiv.attr.classes:insert("callout-style-" .. calloutAppearance)
+ if node.type ~= nil then
+ calloutDiv.attr.classes:insert("callout-" .. node.type)
end
- local appearance = nameForCalloutStyle(appearanceRaw);
- if appearance == "minimal" then
- icon = false
- appearance = "simple"
+ -- the image placeholder
+ local noicon = ""
+
+ -- Check to see whether this is a recognized type
+ if icon == false or not _quarto.modules.callouts.isBuiltInType(callout_type) or type == nil then
+ noicon = " no-icon"
+ calloutDiv.attr.classes:insert("no-icon")
end
- local content = pandoc.Blocks({})
- content:extend(quarto.utils.as_blocks(tbl.content))
- local title = tbl.title
- if type(title) == "string" then
- title = pandoc.Str(title)
+ local imgPlaceholder = pandoc.Plain({pandoc.RawInline("html", "")});
+ local imgDiv = pandoc.Div({imgPlaceholder}, pandoc.Attr("", {"callout-icon-container"}));
+
+ -- show a titled callout
+ if title ~= nil and (pandoc.utils.type(title) == "string" or next(title) ~= nil) then
+
+ -- mark the callout as being titleed
+ calloutDiv.attr.classes:insert("callout-titled")
+
+ -- create a unique id for the callout
+ local calloutid = "callout-" .. calloutidx
+ calloutidx = calloutidx + 1
+
+ -- create the header to contain the title
+ -- title should expand to fill its space
+ local titleDiv = pandoc.Div(pandoc.Plain(title), pandoc.Attr("", {"callout-title-container", "flex-fill"}))
+ local headerDiv = pandoc.Div({imgDiv, titleDiv}, pandoc.Attr("", {"callout-header", "d-flex", "align-content-center"}))
+ local bodyDiv = div
+ bodyDiv.attr.classes:insert("callout-body")
+
+ if collapse ~= nil then
+
+ -- collapse default value
+ local expandedAttrVal = "true"
+ if collapse == "true" or collapse == true then
+ expandedAttrVal = "false"
+ end
+
+ -- create the collapse button
+ local btnClasses = "callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"
+ local btnIcon = ""
+ local toggleButton = pandoc.RawInline("html", "
" .. btnIcon .. "
")
+ headerDiv.content:insert(pandoc.Plain(toggleButton));
+
+ -- configure the header div for collapse
+ local bsTargetClz = calloutid .. "-contents"
+ headerDiv.attr.attributes["bs-toggle"] = "collapse"
+ headerDiv.attr.attributes["bs-target"] = "." .. bsTargetClz
+ headerDiv.attr.attributes["aria-controls"] = calloutid
+ headerDiv.attr.attributes["aria-expanded"] = expandedAttrVal
+ headerDiv.attr.attributes["aria-label"] = 'Toggle callout'
+
+ -- configure the body div for collapse
+ local collapseDiv = pandoc.Div({})
+ collapseDiv.attr.identifier = calloutid
+ collapseDiv.attr.classes:insert(bsTargetClz)
+ collapseDiv.attr.classes:insert("callout-collapse")
+ collapseDiv.attr.classes:insert("collapse")
+ if expandedAttrVal == "true" then
+ collapseDiv.attr.classes:insert("show")
+ end
+
+ -- add the current body to the collapse div and use the collapse div instead
+ collapseDiv.content:insert(bodyDiv)
+ bodyDiv = collapseDiv
+ end
+
+ -- add the header and body to the div
+ calloutDiv.content:insert(headerDiv)
+ calloutDiv.content:insert(bodyDiv)
+ else
+ -- show an untitleed callout
+
+ -- create a card body
+ local containerDiv = pandoc.Div({imgDiv, div}, pandoc.Attr("", {"callout-body"}))
+ containerDiv.attr.classes:insert("d-flex")
+
+ -- add the container to the callout card
+ calloutDiv.content:insert(containerDiv)
end
- return {
- title = title,
- collapse = tbl.collapse,
- content = content,
- appearance = appearance,
- icon = icon,
- type = t,
- attr = tbl.attr or pandoc.Attr(),
- }
+
+ return calloutDiv
+ end
+
+
+
+ _quarto.ast.add_handler({
+ -- use either string or array of strings
+ class_name = { "callout", "callout-note", "callout-warning", "callout-important", "callout-caution", "callout-tip" },
+
+ -- the name of the ast node, used as a key in extended ast filter tables
+ ast_name = "Callout",
+
+ -- callouts will be rendered as blocks
+ kind = "Block",
+
+ -- a function that takes the div node as supplied in user markdown
+ -- and returns the custom node
+ parse = function(div)
+ quarto_global_state.hasCallouts = true
+ local title = markdownToInlines(div.attr.attributes["title"])
+ if not title or #title == 0 then
+ title = resolveHeadingCaption(div)
+ end
+ local old_attr = div.attr
+ local appearanceRaw = div.attr.attributes["appearance"]
+ local icon = div.attr.attributes["icon"]
+ local collapse = div.attr.attributes["collapse"]
+ div.attr.attributes["appearance"] = nil
+ div.attr.attributes["collapse"] = nil
+ div.attr.attributes["icon"] = nil
+ local callout_type = calloutType(div)
+ div.attr.classes = div.attr.classes:filter(function(class) return not _quarto.modules.classpredicates.isCallout(class) end)
+ return quarto.Callout({
+ appearance = appearanceRaw,
+ title = title,
+ collapse = collapse,
+ content = div.content,
+ icon = icon,
+ type = callout_type,
+ attr = old_attr,
+ })
+ end,
+
+ -- These fields will be stored in the extended ast node
+ -- and available in the object passed to the custom filters
+ -- They must store Pandoc AST data. "Inline" custom nodes
+ -- can store Inlines in these fields, "Block" custom nodes
+ -- can store Blocks (and hence also Inlines implicitly).
+ slots = { "title", "content" },
+
+ constructor = function(tbl)
+ quarto_global_state.hasCallouts = true
+
+ local t = tbl.type
+ local iconDefault = true
+ local appearanceDefault = nil
+ if t == "none" then
+ iconDefault = false
+ appearanceDefault = "simple"
+ end
+ local appearanceRaw = tbl.appearance
+ if appearanceRaw == nil then
+ appearanceRaw = option("callout-appearance", appearanceDefault)
+ end
+
+ local icon = tbl.icon
+ if icon == nil then
+ icon = option("callout-icon", iconDefault)
+ elseif icon == "false" then
+ icon = false
+ end
+
+ local appearance = nameForCalloutStyle(appearanceRaw);
+ if appearance == "minimal" then
+ icon = false
+ appearance = "simple"
+ end
+ local content = pandoc.Blocks({})
+ content:extend(quarto.utils.as_blocks(tbl.content))
+ local title = tbl.title
+ if type(title) == "string" then
+ title = pandoc.Str(title)
+ end
+ return {
+ title = title,
+ collapse = tbl.collapse,
+ content = content,
+ appearance = appearance,
+ icon = icon,
+ type = t,
+ attr = tbl.attr or pandoc.Attr(),
+ }
+ end
+ })
+
+ -- default renderer first
+ _quarto.ast.add_renderer("Callout", function(_)
+ return true
+ end, function(node)
+ node = _quarto.modules.callouts.decorate_callout_title_with_crossref(node)
+ local contents = _quarto.modules.callouts.resolveCalloutContents(node, true)
+ local callout = pandoc.BlockQuote(contents)
+ local result = pandoc.Div(callout, pandoc.Attr(node.attr.identifier or ""))
+ return result
+ end)
+
+ _quarto.ast.add_renderer("Callout", function(_)
+ return _quarto.format.isHtmlOutput() and hasBootstrap()
+ end, calloutDiv)
+
+ _quarto.ast.add_renderer("Callout", function(_)
+ return _quarto.format.isEpubOutput() or _quarto.format.isRevealJsOutput()
+ end, function (node)
+ local title = quarto.utils.as_inlines(node.title)
+ local type = node.type
+ local calloutAppearance = node.appearance
+ local hasIcon = node.icon
+
+ if calloutAppearance == _quarto.modules.constants.kCalloutAppearanceDefault and pandoc.utils.stringify(title) == "" then
+ title = _quarto.modules.callouts.displayName(type)
+ end
+
+ -- the body of the callout
+ local calloutBody = pandoc.Div({}, pandoc.Attr("", {"callout-body"}))
+
+ local imgPlaceholder = pandoc.Plain({pandoc.RawInline("html", "")});
+ local imgDiv = pandoc.Div({imgPlaceholder}, pandoc.Attr("", {"callout-icon-container"}));
+
+ -- title
+ if title ~= nil and (pandoc.utils.type(title) == "string" or next(title) ~= nil) then
+ local callout_title = pandoc.Div({}, pandoc.Attr("", {"callout-title"}))
+ if hasIcon then
+ callout_title.content:insert(imgDiv)
+ end
+ callout_title.content:insert(pandoc.Para(pandoc.Strong(title)))
+ calloutBody.content:insert(callout_title)
+ else
+ if hasIcon then
+ calloutBody.content:insert(imgDiv)
+ end
+ end
+
+ -- contents
+ local calloutContents = pandoc.Div(node.content, pandoc.Attr("", {"callout-content"}))
+ calloutBody.content:insert(calloutContents)
+
+ -- set attributes (including hiding icon)
+ local attributes = pandoc.List({"callout"})
+ if type ~= nil then
+ attributes:insert("callout-" .. type)
+ end
+
+ if hasIcon == false then
+ attributes:insert("no-icon")
+ end
+ if title ~= nil and (pandoc.utils.type(title) == "string" or next(title) ~= nil) then
+ attributes:insert("callout-titled")
+ end
+ attributes:insert("callout-style-" .. calloutAppearance)
+
+ local result = pandoc.Div({ calloutBody }, pandoc.Attr(node.attr.identifier or "", attributes))
+ -- in revealjs or epub, if the leftover attr is non-trivial,
+ -- then we need to wrap the callout in a div (#5208, #6853)
+ if node.attr.identifier ~= "" or #node.attr.classes > 0 or #node.attr.attributes > 0 then
+ return pandoc.Div({ result }, node.attr)
+ else
+ return result
+ end
+ end)
+
+ _quarto.ast.add_renderer("Callout", function(_)
+ return _quarto.format.isGithubMarkdownOutput()
+ end, function(callout)
+ local result = pandoc.Blocks({})
+ local header = "[!" .. callout.type:upper() .. "]"
+ result:insert(pandoc.RawBlock("markdown", header))
+ local tt = pandoc.utils.type(callout.title)
+ if tt ~= "nil" then
+ result:insert(pandoc.Header(3, quarto.utils.as_inlines(callout.title)))
+ end
+ local ct = pandoc.utils.type(callout.content)
+ if ct == "Block" then
+ result:insert(callout.content)
+ elseif ct == "Blocks" then
+ result:extend(callout.content)
+ else
+ internal_error()
+ end
+ return pandoc.BlockQuote(result)
+ end)
+
+ local included_font_awesome = false
+ local function ensure_typst_font_awesome()
+ if included_font_awesome then
+ return
+ end
+ included_font_awesome = true
+ quarto.doc.include_text("in-header", "#import \"@preview/fontawesome:0.1.0\": *")
end
-})
-local calloutidx = 1
+ _quarto.ast.add_renderer("Callout", function(_)
+ return _quarto.format.isTypstOutput()
+ end, function(callout)
+ ensure_typst_font_awesome()
+
+ local attrs = _quarto.modules.callouts.callout_attrs[callout.type]
+ local background_color, icon_color, icon
+ if attrs == nil then
+ background_color = "white"
+ icon_color = "black"
+ icon = "fa-info"
+ else
+ background_color = "rgb(\"#" .. attrs.background_color .. "\")";
+ icon_color = "rgb(\"#" .. attrs.color .. "\")";
+ icon = attrs.fa_icon_typst
+ end
+
+ local title = callout.title
+ if title == nil then
+ title = pandoc.Plain(_quarto.modules.callouts.displayName(callout.type))
+ end
+
+ local typst_callout = _quarto.format.typst.function_call("callout", {
+ { "body", _quarto.format.typst.as_typst_content(callout.content) },
+ { "title", _quarto.format.typst.as_typst_content(title) },
+ { "background_color", pandoc.RawInline("typst", background_color) },
+ { "icon_color", pandoc.RawInline("typst", icon_color) },
+ { "icon", pandoc.RawInline("typst", "" .. icon .. "()")}
+ })
+
+ if callout.attr.identifier == "" then
+ return typst_callout
+ end
+
+ local category = crossref.categories.by_ref_type[refType(callout.attr.identifier)]
+ return make_typst_figure {
+ content = typst_callout,
+ caption_location = "top",
+ caption = pandoc.Plain(pandoc.Str("")),
+ kind = "quarto-callout-" .. callout.type,
+ supplement = category.name,
+ numbering = "1",
+ identifier = callout.attr.identifier
+ }
+ end)
+
+ _quarto.ast.add_renderer("Callout", function(_)
+ return _quarto.format.isDocxOutput()
+ end, function(callout)
+ return calloutDocx(callout)
+ end)
+end
+_callout_main()
function docx_callout_and_table_fixup()
if not _quarto.format.isDocxOutput() then
return {}
end
+ -- Attempts to detect whether this element is a code cell
+ -- whose output is a table
+ local function isCodeCellTable(el)
+ local isTable = false
+ _quarto.ast.walk(el, {
+ Div = function(div)
+ if div.attr.classes:find_if(_quarto.modules.classpredicates.isCodeCellDisplay) then
+ _quarto.ast.walk(div, {
+ Table = function(tbl)
+ isTable = true
+ end
+ })
+ end
+ end
+ })
+ return isTable
+ end
+
+ local function isCodeCellFigure(el)
+ local isFigure = false
+ _quarto.ast.walk(el, {
+ Div = function(div)
+ if div.attr.classes:find_if(_quarto.modules.classpredicates.isCodeCellDisplay) then
+ if (isFigureDiv(div)) then
+ isFigure = true
+ elseif div.content and #div.content > 0 then
+ isFigure = discoverFigure(div.content[1], true) ~= nil
+ end
+ end
+ end
+ })
+ return isFigure
+ end
+
return {
-- Insert paragraphs between consecutive callouts or tables for docx
@@ -127,7 +475,7 @@ function docx_callout_and_table_fixup()
local isCodeBlock = el.t == "CodeBlock"
-- Determine whether this is a code cell that outputs a table
- local isCodeCell = is_regular_node(el, "Div") and el.attr.classes:find_if(isCodeCell)
+ local isCodeCell = is_regular_node(el, "Div") and el.attr.classes:find_if(_quarto.modules.classpredicates.isCodeCell)
if isCodeCell and (isCodeCellTable(el) or isCodeCellFigure(el)) then
isTableOrFigure = true
end
@@ -166,565 +514,6 @@ function docx_callout_and_table_fixup()
}
end
-function isCallout(class)
- return class == 'callout' or class:match("^callout%-")
-end
-
-function isDocxCallout(class)
- return class == "docx-callout"
-end
-
-function isCodeCell(class)
- return class == "cell"
-end
-
-function isCodeCellDisplay(class)
- return class == "cell-output-display"
-end
-
--- Attempts to detect whether this element is a code cell
--- whose output is a table
-function isCodeCellTable(el)
- local isTable = false
- _quarto.ast.walk(el, {
- Div = function(div)
- if div.attr.classes:find_if(isCodeCellDisplay) then
- _quarto.ast.walk(div, {
- Table = function(tbl)
- isTable = true
- end
- })
- end
- end
- })
- return isTable
-end
-
-function isCodeCellFigure(el)
- local isFigure = false
- _quarto.ast.walk(el, {
- Div = function(div)
- if div.attr.classes:find_if(isCodeCellDisplay) then
- if (isFigureDiv(div)) then
- isFigure = true
- elseif div.content and #div.content > 0 then
- isFigure = discoverFigure(div.content[1], true) ~= nil
- end
- end
- end
- })
- return isFigure
-end
-
-local function callout_title_prefix(callout, withDelimiter)
- local category = crossref.categories.by_ref_type[refType(callout.attr.identifier)]
- if category == nil then
- fail("unknown callout prefix '" .. refType(callout.attr.identifier) .. "'")
- return
- end
-
- return titlePrefix(category.ref_type, category.name, callout.order, withDelimiter)
-end
-
-function decorate_callout_title_with_crossref(callout)
- callout = ensure_custom(callout)
- if not param("enable-crossref", true) then
- -- don't decorate captions with crossrefs information if crossrefs are disabled
- return callout
- end
- -- nil should never happen here, but the Lua analyzer doesn't know it
- if callout == nil then
- -- luacov: disable
- internal_error()
- -- luacov: enable
- return callout
- end
- if not is_valid_ref_type(refType(callout.attr.identifier)) then
- return callout
- end
- if callout.title == nil then
- callout.title = pandoc.Plain({})
- end
- local title = callout.title.content
-
- -- unlabeled callouts do not get a title prefix
- local is_uncaptioned = not ((title ~= nil) and (#title > 0))
- -- this is a hack but we need it to control styling downstream
- callout.is_uncaptioned = is_uncaptioned
- local title_prefix = callout_title_prefix(callout, not is_uncaptioned)
- tprepend(title, title_prefix)
-
- return callout
-end
-
--- an HTML callout div
-function calloutDiv(node)
- node = decorate_callout_title_with_crossref(node)
-
- -- the first heading is the title
- local div = pandoc.Div({})
- local c = quarto.utils.as_blocks(node.content)
- if pandoc.utils.type(c) == "Blocks" then
- div.content:extend(c)
- else
- div.content:insert(c)
- end
- local title = quarto.utils.as_inlines(node.title)
- local callout_type = node.type
- local calloutAppearance = node.appearance
- local icon = node.icon
- local collapse = node.collapse
-
- if calloutAppearance == constants.kCalloutAppearanceDefault and pandoc.utils.stringify(title) == "" then
- title = quarto.utils.as_inlines(pandoc.Plain(displayName(node.type)))
- end
-
- -- Make an outer card div and transfer classes and id
- local calloutDiv = pandoc.Div({})
- calloutDiv.attr = node.attr:clone()
-
- local identifier = node.attr.identifier
- if identifier ~= "" then
- node.attr.identifier = ""
- calloutDiv.attr.identifier = identifier
- -- inject an anchor so callouts can be linked to
- -- local attr = pandoc.Attr(identifier, {}, {})
- -- local anchor = pandoc.Link({}, "", "", attr)
- -- title:insert(1, anchor)
- end
-
- div.attr.classes = pandoc.List()
- div.attr.classes:insert("callout-body-container")
-
- -- add card attribute
- calloutDiv.attr.classes:insert("callout")
- calloutDiv.attr.classes:insert("callout-style-" .. calloutAppearance)
- if node.type ~= nil then
- calloutDiv.attr.classes:insert("callout-" .. node.type)
- end
-
- -- the image placeholder
- local noicon = ""
-
- -- Check to see whether this is a recognized type
- if icon == false or not isBuiltInType(callout_type) or type == nil then
- noicon = " no-icon"
- calloutDiv.attr.classes:insert("no-icon")
- end
- local imgPlaceholder = pandoc.Plain({pandoc.RawInline("html", "")});
- local imgDiv = pandoc.Div({imgPlaceholder}, pandoc.Attr("", {"callout-icon-container"}));
-
- -- show a titled callout
- if title ~= nil and (pandoc.utils.type(title) == "string" or next(title) ~= nil) then
-
- -- mark the callout as being titleed
- calloutDiv.attr.classes:insert("callout-titled")
-
- -- create a unique id for the callout
- local calloutid = "callout-" .. calloutidx
- calloutidx = calloutidx + 1
-
- -- create the header to contain the title
- -- title should expand to fill its space
- local titleDiv = pandoc.Div(pandoc.Plain(title), pandoc.Attr("", {"callout-title-container", "flex-fill"}))
- local headerDiv = pandoc.Div({imgDiv, titleDiv}, pandoc.Attr("", {"callout-header", "d-flex", "align-content-center"}))
- local bodyDiv = div
- bodyDiv.attr.classes:insert("callout-body")
-
- if collapse ~= nil then
-
- -- collapse default value
- local expandedAttrVal = "true"
- if collapse == "true" or collapse == true then
- expandedAttrVal = "false"
- end
-
- -- create the collapse button
- local btnClasses = "callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"
- local btnIcon = ""
- local toggleButton = pandoc.RawInline("html", "" .. btnIcon .. "
")
- headerDiv.content:insert(pandoc.Plain(toggleButton));
-
- -- configure the header div for collapse
- local bsTargetClz = calloutid .. "-contents"
- headerDiv.attr.attributes["bs-toggle"] = "collapse"
- headerDiv.attr.attributes["bs-target"] = "." .. bsTargetClz
- headerDiv.attr.attributes["aria-controls"] = calloutid
- headerDiv.attr.attributes["aria-expanded"] = expandedAttrVal
- headerDiv.attr.attributes["aria-label"] = 'Toggle callout'
-
- -- configure the body div for collapse
- local collapseDiv = pandoc.Div({})
- collapseDiv.attr.identifier = calloutid
- collapseDiv.attr.classes:insert(bsTargetClz)
- collapseDiv.attr.classes:insert("callout-collapse")
- collapseDiv.attr.classes:insert("collapse")
- if expandedAttrVal == "true" then
- collapseDiv.attr.classes:insert("show")
- end
-
- -- add the current body to the collapse div and use the collapse div instead
- collapseDiv.content:insert(bodyDiv)
- bodyDiv = collapseDiv
- end
-
- -- add the header and body to the div
- calloutDiv.content:insert(headerDiv)
- calloutDiv.content:insert(bodyDiv)
- else
- -- show an untitleed callout
-
- -- create a card body
- local containerDiv = pandoc.Div({imgDiv, div}, pandoc.Attr("", {"callout-body"}))
- containerDiv.attr.classes:insert("d-flex")
-
- -- add the container to the callout card
- calloutDiv.content:insert(containerDiv)
- end
-
- return calloutDiv
-end
-
-function epubCallout(node)
- local title = quarto.utils.as_inlines(node.title)
- local type = node.type
- local calloutAppearance = node.appearance
- local hasIcon = node.icon
-
- if calloutAppearance == constants.kCalloutAppearanceDefault and pandoc.utils.stringify(title) == "" then
- title = displayName(type)
- end
-
- -- the body of the callout
- local calloutBody = pandoc.Div({}, pandoc.Attr("", {"callout-body"}))
-
- local imgPlaceholder = pandoc.Plain({pandoc.RawInline("html", "")});
- local imgDiv = pandoc.Div({imgPlaceholder}, pandoc.Attr("", {"callout-icon-container"}));
-
- -- title
- if title ~= nil and (pandoc.utils.type(title) == "string" or next(title) ~= nil) then
- local callout_title = pandoc.Div({}, pandoc.Attr("", {"callout-title"}))
- if hasIcon then
- callout_title.content:insert(imgDiv)
- end
- callout_title.content:insert(pandoc.Para(pandoc.Strong(title)))
- calloutBody.content:insert(callout_title)
- else
- if hasIcon then
- calloutBody.content:insert(imgDiv)
- end
- end
-
- -- contents
- local calloutContents = pandoc.Div(node.content, pandoc.Attr("", {"callout-content"}))
- calloutBody.content:insert(calloutContents)
-
- -- set attributes (including hiding icon)
- local attributes = pandoc.List({"callout"})
- if type ~= nil then
- attributes:insert("callout-" .. type)
- end
-
- if hasIcon == false then
- attributes:insert("no-icon")
- end
- if title ~= nil and (pandoc.utils.type(title) == "string" or next(title) ~= nil) then
- attributes:insert("callout-titled")
- end
- attributes:insert("callout-style-" .. calloutAppearance)
-
- local result = pandoc.Div({ calloutBody }, pandoc.Attr(node.attr.identifier or "", attributes))
- -- in revealjs or epub, if the leftover attr is non-trivial,
- -- then we need to wrap the callout in a div (#5208, #6853)
- if node.attr.identifier ~= "" or #node.attr.classes > 0 or #node.attr.attributes > 0 then
- return pandoc.Div({ result }, node.attr)
- else
- return result
- end
-
-end
-
-function simpleCallout(node)
- node = decorate_callout_title_with_crossref(node)
- local contents = resolveCalloutContents(node, true)
- local callout = pandoc.BlockQuote(contents)
- local result = pandoc.Div(callout, pandoc.Attr(node.attr.identifier or ""))
- return result
-end
-
-function resolveCalloutContents(node, require_title)
- local title = quarto.utils.as_inlines(node.title)
- local type = node.type
-
- local contents = pandoc.List({})
-
- -- Add the titles and contents
- -- class_name
- if pandoc.utils.stringify(title) == "" and require_title then
- ---@diagnostic disable-next-line: need-check-nil
- title = stringToInlines(type:sub(1,1):upper()..type:sub(2))
- end
-
- -- raw paragraph with styles (left border, colored)
- if title ~= nil then
- contents:insert(pandoc.Para(pandoc.Strong(title)))
- end
- tappend(contents, quarto.utils.as_blocks(node.content))
-
- return contents
-end
-
-function removeParagraphPadding(contents)
- if #contents > 0 then
-
- if #contents == 1 then
- if contents[1].t == "Para" then
- contents[1] = openXmlPara(contents[1], 'w:before="16" w:after="16"')
- end
- else
- if contents[1].t == "Para" then
- contents[1] = openXmlPara(contents[1], 'w:before="16"')
- end
-
- if contents[#contents].t == "Para" then
- contents[#contents] = openXmlPara(contents[#contents], 'w:after="16"')
- end
- end
- end
-end
-
-function openXmlPara(para, spacing)
- local xmlPara = pandoc.Para({
- pandoc.RawInline("openxml", "\n\n")
- })
- tappend(xmlPara.content, para.content)
- return xmlPara
-end
-
-function nameForCalloutStyle(calloutType)
- if calloutType == nil then
- return "default"
- else
- local name = pandoc.utils.stringify(calloutType);
-
- if name:lower() == "minimal" then
- return "minimal"
- elseif name:lower() == "simple" then
- return "simple"
- else
- return "default"
- end
- end
-end
-
-local kDefaultDpi = 96
-function docxCalloutImage(type)
-
- -- If the DPI has been changed, we need to scale the callout icon
- local dpi = pandoc.WriterOptions(PANDOC_WRITER_OPTIONS)['dpi']
- local scaleFactor = 1
- if dpi ~= nil then
- scaleFactor = dpi / kDefaultDpi
- end
-
- -- try to form the svg name
- local svg = nil
- if type ~= nil then
- svg = param("icon-" .. type, nil)
- end
-
- -- lookup the image
- if svg ~= nil then
- local img = pandoc.Image({}, svg, '', {[constants.kProjectResolverIgnore]="true"})
- img.attr.attributes["width"] = tostring(16 * scaleFactor)
- img.attr.attributes["height"] = tostring(16 * scaleFactor)
- return img
- else
- return nil
- end
-end
-
-local callout_attrs = {
- note = {
- color = kColorNote,
- background_color = kBackgroundColorNote,
- latex_color = "quarto-callout-note-color",
- latex_frame_color = "quarto-callout-note-color-frame",
- fa_icon = "faInfo",
- fa_icon_typst = "fa-info"
- },
- warning = {
- color = kColorWarning,
- background_color = kBackgroundColorWarning,
- latex_color = "quarto-callout-warning-color",
- latex_frame_color = "quarto-callout-warning-color-frame",
- fa_icon = "faExclamationTriangle",
- fa_icon_typst = "fa-exclamation-triangle"
- },
- important = {
- color = kColorImportant,
- background_color = kBackgroundColorImportant,
- latex_color = "quarto-callout-important-color",
- latex_frame_color = "quarto-callout-important-color-frame",
- fa_icon = "faExclamation",
- fa_icon_typst = "fa-exclamation"
- },
- caution = {
- color = kColorCaution,
- background_color = kBackgroundColorCaution,
- latex_color = "quarto-callout-caution-color",
- latex_frame_color = "quarto-callout-caution-color-frame",
- fa_icon = "faFire",
- fa_icon_typst = "fa-fire"
- },
- tip = {
- color = kColorTip,
- background_color = kBackgroundColorTip,
- latex_color = "quarto-callout-tip-color",
- latex_frame_color = "quarto-callout-tip-color-frame",
- fa_icon = "faLightbulb",
- fa_icon_typst = "fa-lightbulb"
- },
-
- __other = {
- color = kColorUnknown,
- background_color = kColorUnknown,
- latex_color = "quarto-callout-color",
- latex_frame_color = "quarto-callout-color-frame",
- fa_icon = nil,
- fa_icon_typst = nil
- }
-}
-
-setmetatable(callout_attrs, {
- __index = function(tbl, key)
- return tbl.__other
- end
-})
-
-function htmlColorForType(type)
- return callout_attrs[type].color
-end
-
-function htmlBackgroundColorForType(type)
- return callout_attrs[type].background_color
-end
-
-function latexColorForType(type)
- return callout_attrs[type].latex_color
-end
-
-function latexFrameColorForType(type)
- return callout_attrs[type].latex_frame_color
-end
-
-function iconForType(type)
- return callout_attrs[type].fa_icon
-end
-
-function isBuiltInType(type)
- local icon = iconForType(type)
- return icon ~= nil
-end
-
-function displayName(type)
- local defaultName = type:sub(1,1):upper()..type:sub(2)
- return param("callout-" .. type .. "-title", defaultName)
-end
-
--- default renderer first
-_quarto.ast.add_renderer("Callout", function(_)
- return true
-end, simpleCallout)
-_quarto.ast.add_renderer("Callout", function(_)
- return _quarto.format.isHtmlOutput() and hasBootstrap()
-end, calloutDiv)
-_quarto.ast.add_renderer("Callout", function(_)
- return _quarto.format.isEpubOutput() or _quarto.format.isRevealJsOutput()
-end, epubCallout)
-
-_quarto.ast.add_renderer("Callout", function(_)
- return _quarto.format.isGithubMarkdownOutput()
-end, function(callout)
- local result = pandoc.Blocks({})
- local header = "[!" .. callout.type:upper() .. "]"
- result:insert(pandoc.RawBlock("markdown", header))
- local tt = pandoc.utils.type(callout.title)
- if tt ~= "nil" then
- result:insert(pandoc.Header(3, quarto.utils.as_inlines(callout.title)))
- end
- local ct = pandoc.utils.type(callout.content)
- if ct == "Block" then
- result:insert(callout.content)
- elseif ct == "Blocks" then
- result:extend(callout.content)
- else
- internal_error()
- end
- return pandoc.BlockQuote(result)
-end)
-
-local included_font_awesome = false
-local function ensure_typst_font_awesome()
- if included_font_awesome then
- return
- end
- included_font_awesome = true
- quarto.doc.include_text("in-header", "#import \"@preview/fontawesome:0.1.0\": *")
-end
-
-_quarto.ast.add_renderer("Callout", function(_)
- return _quarto.format.isTypstOutput()
-end, function(callout)
- ensure_typst_font_awesome()
-
- local attrs = callout_attrs[callout.type]
- local background_color, icon_color, icon
- if attrs == nil then
- background_color = "white"
- icon_color = "black"
- icon = "fa-info"
- else
- background_color = "rgb(\"#" .. attrs.background_color .. "\")";
- icon_color = "rgb(\"#" .. attrs.color .. "\")";
- icon = attrs.fa_icon_typst
- end
-
- local title = callout.title
- if title == nil then
- title = pandoc.Plain(displayName(callout.type))
- end
-
- local typst_callout = _quarto.format.typst.function_call("callout", {
- { "body", _quarto.format.typst.as_typst_content(callout.content) },
- { "title", _quarto.format.typst.as_typst_content(title) },
- { "background_color", pandoc.RawInline("typst", background_color) },
- { "icon_color", pandoc.RawInline("typst", icon_color) },
- { "icon", pandoc.RawInline("typst", "" .. icon .. "()")}
- })
-
- if callout.attr.identifier == "" then
- return typst_callout
- end
-
- local category = crossref.categories.by_ref_type[refType(callout.attr.identifier)]
- return make_typst_figure {
- content = typst_callout,
- caption_location = "top",
- caption = pandoc.Plain(pandoc.Str("")),
- kind = "quarto-callout-" .. callout.type,
- supplement = category.name,
- numbering = "1",
- identifier = callout.attr.identifier
- }
-end)
-
-_quarto.ast.add_renderer("Callout", function(_)
- return _quarto.format.isDocxOutput()
-end, function(callout)
- return calloutDocx(callout)
-end)
-
function crossref_callouts()
return {
Callout = function(callout)
diff --git a/src/resources/filters/main.lua b/src/resources/filters/main.lua
index 6b57fda6af..848a2b0d3e 100644
--- a/src/resources/filters/main.lua
+++ b/src/resources/filters/main.lua
@@ -12,6 +12,8 @@ end
import("./mainstateinit.lua")
+import("./modules/import_all.lua")
+
import("./ast/scopedwalk.lua")
import("./ast/customnodes.lua")
import("./ast/emulatedfilter.lua")
diff --git a/src/resources/filters/modules/authors.lua b/src/resources/filters/modules/authors.lua
index 2c000f81c5..5da7e6a39b 100644
--- a/src/resources/filters/modules/authors.lua
+++ b/src/resources/filters/modules/authors.lua
@@ -1,5 +1,5 @@
--- meta.lua
--- Copyright (C) 2020-2022 Posit Software, PBC
+-- authors.lua
+-- Copyright (C) 2020-2024 Posit Software, PBC
-- read and replace the authors field
-- without reshaped data that has been
diff --git a/src/resources/filters/modules/callouts.lua b/src/resources/filters/modules/callouts.lua
new file mode 100644
index 0000000000..4e228cfb49
--- /dev/null
+++ b/src/resources/filters/modules/callouts.lua
@@ -0,0 +1,199 @@
+-- callouts.lua
+-- Copyright (C) 2024 Posit Software, PBC
+
+local constants = require("modules/constants")
+
+local function callout_title_prefix(callout, withDelimiter)
+ local category = crossref.categories.by_ref_type[refType(callout.attr.identifier)]
+ if category == nil then
+ fail("unknown callout prefix '" .. refType(callout.attr.identifier) .. "'")
+ return
+ end
+
+ return titlePrefix(category.ref_type, category.name, callout.order, withDelimiter)
+end
+
+local function decorate_callout_title_with_crossref(callout)
+ callout = ensure_custom(callout)
+ if not param("enable-crossref", true) then
+ -- don't decorate captions with crossrefs information if crossrefs are disabled
+ return callout
+ end
+ -- nil should never happen here, but the Lua analyzer doesn't know it
+ if callout == nil then
+ -- luacov: disable
+ internal_error()
+ -- luacov: enable
+ return callout
+ end
+ if not is_valid_ref_type(refType(callout.attr.identifier)) then
+ return callout
+ end
+ if callout.title == nil then
+ callout.title = pandoc.Plain({})
+ end
+ local title = callout.title.content
+
+ -- unlabeled callouts do not get a title prefix
+ local is_uncaptioned = not ((title ~= nil) and (#title > 0))
+ -- this is a hack but we need it to control styling downstream
+ callout.is_uncaptioned = is_uncaptioned
+ local title_prefix = callout_title_prefix(callout, not is_uncaptioned)
+ tprepend(title, title_prefix)
+
+ return callout
+end
+
+local function resolveCalloutContents(node, require_title)
+ local title = quarto.utils.as_inlines(node.title)
+ local type = node.type
+
+ local contents = pandoc.List({})
+
+ -- Add the titles and contents
+ -- class_name
+ if pandoc.utils.stringify(title) == "" and require_title then
+ ---@diagnostic disable-next-line: need-check-nil
+ title = stringToInlines(type:sub(1,1):upper()..type:sub(2))
+ end
+
+ -- raw paragraph with styles (left border, colored)
+ if title ~= nil then
+ contents:insert(pandoc.Para(pandoc.Strong(title)))
+ end
+ tappend(contents, quarto.utils.as_blocks(node.content))
+
+ return contents
+end
+
+local kDefaultDpi = 96
+local function docxCalloutImage(type)
+
+ -- If the DPI has been changed, we need to scale the callout icon
+ local dpi = pandoc.WriterOptions(PANDOC_WRITER_OPTIONS)['dpi']
+ local scaleFactor = 1
+ if dpi ~= nil then
+ scaleFactor = dpi / kDefaultDpi
+ end
+
+ -- try to form the svg name
+ local svg = nil
+ if type ~= nil then
+ svg = param("icon-" .. type, nil)
+ end
+
+ -- lookup the image
+ if svg ~= nil then
+ local img = pandoc.Image({}, svg, '', {[constants.kProjectResolverIgnore]="true"})
+ img.attr.attributes["width"] = tostring(16 * scaleFactor)
+ img.attr.attributes["height"] = tostring(16 * scaleFactor)
+ return img
+ else
+ return nil
+ end
+end
+
+local callout_attrs = {
+ note = {
+ color = constants.kColorNote,
+ background_color = constants.kBackgroundColorNote,
+ latex_color = "quarto-callout-note-color",
+ latex_frame_color = "quarto-callout-note-color-frame",
+ fa_icon = "faInfo",
+ fa_icon_typst = "fa-info"
+ },
+ warning = {
+ color = constants.kColorWarning,
+ background_color = constants.kBackgroundColorWarning,
+ latex_color = "quarto-callout-warning-color",
+ latex_frame_color = "quarto-callout-warning-color-frame",
+ fa_icon = "faExclamationTriangle",
+ fa_icon_typst = "fa-exclamation-triangle"
+ },
+ important = {
+ color = constants.kColorImportant,
+ background_color = constants.kBackgroundColorImportant,
+ latex_color = "quarto-callout-important-color",
+ latex_frame_color = "quarto-callout-important-color-frame",
+ fa_icon = "faExclamation",
+ fa_icon_typst = "fa-exclamation"
+ },
+ caution = {
+ color = constants.kColorCaution,
+ background_color = constants.kBackgroundColorCaution,
+ latex_color = "quarto-callout-caution-color",
+ latex_frame_color = "quarto-callout-caution-color-frame",
+ fa_icon = "faFire",
+ fa_icon_typst = "fa-fire"
+ },
+ tip = {
+ color = constants.kColorTip,
+ background_color = constants.kBackgroundColorTip,
+ latex_color = "quarto-callout-tip-color",
+ latex_frame_color = "quarto-callout-tip-color-frame",
+ fa_icon = "faLightbulb",
+ fa_icon_typst = "fa-lightbulb"
+ },
+
+ __other = {
+ color = constants.kColorUnknown,
+ background_color = constants.kColorUnknown,
+ latex_color = "quarto-callout-color",
+ latex_frame_color = "quarto-callout-color-frame",
+ fa_icon = nil,
+ fa_icon_typst = nil
+ }
+}
+
+setmetatable(callout_attrs, {
+ __index = function(tbl, key)
+ return tbl.__other
+ end
+})
+
+local function htmlColorForType(type)
+ return callout_attrs[type].color
+end
+
+local function htmlBackgroundColorForType(type)
+ return callout_attrs[type].background_color
+end
+
+local function latexColorForType(type)
+ return callout_attrs[type].latex_color
+end
+
+local function latexFrameColorForType(type)
+ return callout_attrs[type].latex_frame_color
+end
+
+local function iconForType(type)
+ return callout_attrs[type].fa_icon
+end
+
+local function isBuiltInType(type)
+ local icon = iconForType(type)
+ return icon ~= nil
+end
+
+local function displayName(type)
+ local defaultName = type:sub(1,1):upper()..type:sub(2)
+ return param("callout-" .. type .. "-title", defaultName)
+end
+
+return {
+ decorate_callout_title_with_crossref = decorate_callout_title_with_crossref,
+ callout_attrs = callout_attrs,
+
+ -- TODO capitalization
+ resolveCalloutContents = resolveCalloutContents,
+ docxCalloutImage = docxCalloutImage,
+
+ htmlColorForType = htmlColorForType,
+ htmlBackgroundColorForType = htmlBackgroundColorForType,
+ latexColorForType = latexColorForType,
+ latexFrameColorForType = latexFrameColorForType,
+ iconForType = iconForType,
+ isBuiltInType = isBuiltInType,
+ displayName = displayName,
+}
\ No newline at end of file
diff --git a/src/resources/filters/modules/classpredicates.lua b/src/resources/filters/modules/classpredicates.lua
new file mode 100644
index 0000000000..dbf16d91d7
--- /dev/null
+++ b/src/resources/filters/modules/classpredicates.lua
@@ -0,0 +1,35 @@
+-- classpredicates.lua
+-- Copyright (C) 2024 Posit Software, PBC
+
+local function isCell(el)
+ return el.classes:includes("cell")
+end
+
+local function isCodeCellOutput(el)
+ return el.classes:includes("cell-output")
+end
+
+local function isCallout(class)
+ return class == 'callout' or class:match("^callout%-")
+end
+
+local function isDocxCallout(class)
+ return class == "docx-callout"
+end
+
+local function isCodeCell(class)
+ return class == "cell"
+end
+
+local function isCodeCellDisplay(class)
+ return class == "cell-output-display"
+end
+
+return {
+ isCallout = isCallout,
+ isCell = isCell,
+ isCodeCell = isCodeCell,
+ isCodeCellDisplay = isCodeCellDisplay,
+ isCodeCellOutput = isCodeCellOutput,
+ isDocxCallout = isDocxCallout
+}
\ No newline at end of file
diff --git a/src/resources/filters/modules/constants.lua b/src/resources/filters/modules/constants.lua
index 6b1694681f..90cf63f1a9 100644
--- a/src/resources/filters/modules/constants.lua
+++ b/src/resources/filters/modules/constants.lua
@@ -131,6 +131,30 @@ local kLangCommentChars = {
}
local kDefaultCodeAnnotationComment = {"#"}
+-- These colors are used as background colors with an opacity of 0.75
+local kColorUnknown = "909090"
+local kColorNote = "0758E5"
+local kColorImportant = "CC1914"
+local kColorWarning = "EB9113"
+local kColorTip = "00A047"
+local kColorCaution = "FC5300"
+
+-- these colors are used with no-opacity
+local kColorUnknownFrame = "acacac"
+local kColorNoteFrame = "4582ec"
+local kColorImportantFrame = "d9534f"
+local kColorWarningFrame = "f0ad4e"
+local kColorTipFrame = "02b875"
+local kColorCautionFrame = "fd7e14"
+
+local kBackgroundColorUnknown = "e6e6e6"
+local kBackgroundColorNote = "dae6fb"
+local kBackgroundColorImportant = "f7dddc"
+local kBackgroundColorWarning = "fcefdc"
+local kBackgroundColorTip = "ccf1e3"
+local kBackgroundColorCaution = "ffe5d0"
+
+
return {
kCitation = kCitation,
kContainerId = kContainerId,
@@ -199,5 +223,24 @@ return {
kLangCommentChars = kLangCommentChars,
kDefaultCodeAnnotationComment = kDefaultCodeAnnotationComment,
kHtmlTableProcessing = kHtmlTableProcessing,
- kHtmlPreTagProcessing = kHtmlPreTagProcessing
+ kHtmlPreTagProcessing = kHtmlPreTagProcessing,
+
+ kColorUnknown = kColorUnknown,
+ kColorNote = kColorNote,
+ kColorImportant = kColorImportant,
+ kColorWarning = kColorWarning,
+ kColorTip = kColorTip,
+ kColorCaution = kColorCaution,
+ kColorUnknownFrame = kColorUnknownFrame,
+ kColorNoteFrame = kColorNoteFrame,
+ kColorImportantFrame = kColorImportantFrame,
+ kColorWarningFrame = kColorWarningFrame,
+ kColorTipFrame = kColorTipFrame,
+ kColorCautionFrame = kColorCautionFrame,
+ kBackgroundColorUnknown = kBackgroundColorUnknown,
+ kBackgroundColorNote = kBackgroundColorNote,
+ kBackgroundColorImportant = kBackgroundColorImportant,
+ kBackgroundColorWarning = kBackgroundColorWarning,
+ kBackgroundColorTip = kBackgroundColorTip,
+ kBackgroundColorCaution = kBackgroundColorCaution,
}
diff --git a/src/resources/filters/modules/import_all.lua b/src/resources/filters/modules/import_all.lua
new file mode 100644
index 0000000000..13f1360e12
--- /dev/null
+++ b/src/resources/filters/modules/import_all.lua
@@ -0,0 +1,22 @@
+-- import_all.lua
+-- imports all modules into _quarto.modules
+
+_quarto.modules = {
+ astshortcode = require("modules/astshortcode"),
+ authors = require("modules/authors"),
+ callouts = require("modules/callouts"),
+ classpredicates = require("modules/classpredicates"),
+ constants = require("modules/constants"),
+ dashboard = require("modules/dashboard"),
+ filenames = require("modules/filenames"),
+ filters = require("modules/filters"),
+ license = require("modules/license"),
+ lightbox = require("modules/lightbox"),
+ mediabag = require("modules/mediabag"),
+ openxml = require("modules/openxml"),
+ patterns = require("modules/patterns"),
+ scope = require("modules/scope"),
+ string = require("modules/string"),
+ tablecolwidths = require("modules/tablecolwidths"),
+ typst = require("modules/typst")
+}
\ No newline at end of file
diff --git a/src/resources/filters/modules/openxml.lua b/src/resources/filters/modules/openxml.lua
new file mode 100644
index 0000000000..d14eda3be2
--- /dev/null
+++ b/src/resources/filters/modules/openxml.lua
@@ -0,0 +1,35 @@
+-- openxml.lua
+-- Copyright (C) 2024 Posit Software, PBC
+
+local function openXmlPara(para, spacing)
+ local xmlPara = pandoc.Para({
+ pandoc.RawInline("openxml", "\n\n")
+ })
+ tappend(xmlPara.content, para.content)
+ return xmlPara
+end
+
+local function removeParagraphPadding(contents)
+ if #contents > 0 then
+
+ if #contents == 1 then
+ if contents[1].t == "Para" then
+ contents[1] = openXmlPara(contents[1], 'w:before="16" w:after="16"')
+ end
+ else
+ if contents[1].t == "Para" then
+ contents[1] = openXmlPara(contents[1], 'w:before="16"')
+ end
+
+ if contents[#contents].t == "Para" then
+ contents[#contents] = openXmlPara(contents[#contents], 'w:after="16"')
+ end
+ end
+ end
+end
+
+return {
+ -- TODO capitalization
+ openXmlPara = openXmlPara,
+ removeParagraphPadding = removeParagraphPadding
+}
\ No newline at end of file
diff --git a/src/resources/filters/quarto-post/docx.lua b/src/resources/filters/quarto-post/docx.lua
index ecd360d53b..d3fab3d020 100644
--- a/src/resources/filters/quarto-post/docx.lua
+++ b/src/resources/filters/quarto-post/docx.lua
@@ -3,201 +3,196 @@
--
-- renders AST nodes to docx
-local constants = require("modules/constants")
-
-local function calloutDocxDefault(node, type, hasIcon)
- local title = quarto.utils.as_inlines(node.title)
- local color = htmlColorForType(type)
- local backgroundColor = htmlBackgroundColorForType(type)
-
- local tablePrefix = [[
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+function calloutDocx(node)
+ local function calloutDocxDefault(node, type, hasIcon)
+ local title = quarto.utils.as_inlines(node.title)
+ local color = _quarto.modules.callouts.htmlColorForType(type)
+ local backgroundColor = _quarto.modules.callouts.htmlBackgroundColorForType(type)
+
+ local tablePrefix = [[
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]
+ local calloutContents = pandoc.List({
+ pandoc.RawBlock("openxml", tablePrefix:gsub('$background', backgroundColor):gsub('$color', color)),
+ })
+
+ -- Create a title if there isn't already one
+ if pandoc.utils.stringify(title) == "" then
+ title = quarto.utils.as_inlines(pandoc.Plain(_quarto.modules.callouts.displayName(node.type)))
+ end
+
+ -- add the image to the title, if needed
+ local calloutImage = _quarto.modules.callouts.docxCalloutImage(type);
+ if hasIcon and calloutImage ~= nil then
+ -- Create a paragraph with the icon, spaces, and text
+ local image_title = pandoc.List({
+ pandoc.RawInline("openxml", '\n\n\n'),
+ calloutImage,
+ pandoc.Space(),
+ pandoc.Space()})
+ tappend(image_title, title)
+ calloutContents:insert(pandoc.Para(image_title))
+ else
+ local titleRaw = _quarto.modules.openxml.openXmlPara(pandoc.Para(title), 'w:before="16" w:after="16"')
+ calloutContents:insert(titleRaw)
+ end
+
+
+ -- end the title row and start the body row
+ local tableMiddle = [[
+
+
+
+
+
+
+
-
-
-
+
+
- ]]
- local calloutContents = pandoc.List({
- pandoc.RawBlock("openxml", tablePrefix:gsub('$background', backgroundColor):gsub('$color', color)),
- })
-
- -- Create a title if there isn't already one
- -- if title == nil then
- -- title = pandoc.List({pandoc.Str(displayName(type))})
- -- end
- if pandoc.utils.stringify(title) == "" then
- title = quarto.utils.as_inlines(pandoc.Plain(displayName(node.type)))
- end
-
- -- add the image to the title, if needed
- local calloutImage = docxCalloutImage(type);
- if hasIcon and calloutImage ~= nil then
- -- Create a paragraph with the icon, spaces, and text
- local image_title = pandoc.List({
- pandoc.RawInline("openxml", '\n\n\n'),
- calloutImage,
- pandoc.Space(),
- pandoc.Space()})
- tappend(image_title, title)
- calloutContents:insert(pandoc.Para(image_title))
- else
- local titleRaw = openXmlPara(pandoc.Para(title), 'w:before="16" w:after="16"')
- calloutContents:insert(titleRaw)
- end
-
- -- end the title row and start the body row
- local tableMiddle = [[
+ ]]
+ calloutContents:insert(pandoc.Div(pandoc.RawBlock("openxml", tableMiddle)))
+
+ -- the main contents of the callout
+ local contents = quarto.utils.as_blocks(node.content)
+
+ -- ensure there are no nested callouts
+ if contents:find_if(function(el)
+ return is_regular_node(el, "Div") and el.attr.classes:find_if(_quarto.modules.classpredicates.isDocxCallout) ~= nil
+ end) ~= nil then
+ fail("Found a nested callout in the document. Please fix this issue and try again.")
+ end
+
+ -- remove padding from existing content and add it
+ _quarto.modules.openxml.removeParagraphPadding(contents)
+ tappend(calloutContents, contents)
+
+ -- close the table
+ local suffix = pandoc.List({pandoc.RawBlock("openxml", [[
-
-
-
-
-
-
-
-
-
-
-
-
-
- ]]
- calloutContents:insert(pandoc.Div(pandoc.RawBlock("openxml", tableMiddle)))
-
- -- the main contents of the callout
- local contents = quarto.utils.as_blocks(node.content)
-
- -- ensure there are no nested callouts
- if contents:find_if(function(el)
- return is_regular_node(el, "Div") and el.attr.classes:find_if(isDocxCallout) ~= nil
- end) ~= nil then
- fail("Found a nested callout in the document. Please fix this issue and try again.")
- end
+
+
+ ]])})
+ tappend(calloutContents, suffix)
- -- remove padding from existing content and add it
- removeParagraphPadding(contents)
- tappend(calloutContents, contents)
-
- -- close the table
- local suffix = pandoc.List({pandoc.RawBlock("openxml", [[
-
-
-
- ]])})
- tappend(calloutContents, suffix)
-
- -- return the callout
- local callout = pandoc.Div(calloutContents, pandoc.Attr("", {"docx-callout"}))
- return callout
-end
-
-
-local function calloutDocxSimple(node, type, hasIcon)
- local color = htmlColorForType(type)
- local title = quarto.utils.as_inlines(node.title)
-
- local tablePrefix = [[
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ]]
-
- local prefix = pandoc.List({
- pandoc.RawBlock("openxml", tablePrefix:gsub('$color', color)),
- })
-
- local calloutImage = docxCalloutImage(type)
- if hasIcon and calloutImage ~= nil then
- local imagePara = pandoc.Para({
- pandoc.RawInline("openxml", '\n\n\n'), calloutImage})
- prefix:insert(pandoc.RawBlock("openxml", ''))
- prefix:insert(imagePara)
- prefix:insert(pandoc.RawBlock("openxml", "\n"))
- else
- prefix:insert(pandoc.RawBlock("openxml", ''))
- end
-
- local suffix = pandoc.List({pandoc.RawBlock("openxml", [[
-
-
-
- ]])})
-
- local calloutContents = pandoc.List({})
- tappend(calloutContents, prefix)
-
- -- deal with the title, if present
- if title ~= nil then
- local titlePara = pandoc.Para(pandoc.Strong(title))
- calloutContents:insert(openXmlPara(titlePara, 'w:before="16" w:after="64"'))
+ -- return the callout
+ local callout = pandoc.Div(calloutContents, pandoc.Attr("", {"docx-callout"}))
+ return callout
end
- -- convert to open xml paragraph
- local contents = pandoc.List({}) -- use as pandoc.List() for find_if
- contents:extend(quarto.utils.as_blocks(node.content))
- removeParagraphPadding(contents)
- -- ensure there are no nested callouts
- if contents:find_if(function(el)
- return is_regular_node(el, "Div") and el.attr.classes:find_if(isDocxCallout) ~= nil
- end) ~= nil then
- fail("Found a nested callout in the document. Please fix this issue and try again.")
+ local function calloutDocxSimple(node, type, hasIcon)
+ local color = _quarto.modules.callouts.htmlColorForType(type)
+ local title = quarto.utils.as_inlines(node.title)
+
+ local tablePrefix = [[
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]]
+
+ local prefix = pandoc.List({
+ pandoc.RawBlock("openxml", tablePrefix:gsub('$color', color)),
+ })
+
+ local calloutImage = _quarto.modules.callouts.docxCalloutImage(type)
+ if hasIcon and calloutImage ~= nil then
+ local imagePara = pandoc.Para({
+ pandoc.RawInline("openxml", '\n\n\n'), calloutImage})
+ prefix:insert(pandoc.RawBlock("openxml", ''))
+ prefix:insert(imagePara)
+ prefix:insert(pandoc.RawBlock("openxml", "\n"))
+ else
+ prefix:insert(pandoc.RawBlock("openxml", ''))
+ end
+
+ local suffix = pandoc.List({pandoc.RawBlock("openxml", [[
+
+
+
+ ]])})
+
+ local calloutContents = pandoc.List({})
+ tappend(calloutContents, prefix)
+
+ -- deal with the title, if present
+ if title ~= nil then
+ local titlePara = pandoc.Para(pandoc.Strong(title))
+ calloutContents:insert(_quarto.modules.openxml.openXmlPara(titlePara, 'w:before="16" w:after="64"'))
+ end
+
+ -- convert to open xml paragraph
+ local contents = pandoc.List({}) -- use as pandoc.List() for find_if
+ contents:extend(quarto.utils.as_blocks(node.content))
+ _quarto.modules.openxml.removeParagraphPadding(contents)
+
+ -- ensure there are no nested callouts
+ if contents:find_if(function(el)
+ return is_regular_node(el, "Div") and el.attr.classes:find_if(_quarto.modules.classpredicates.isDocxCallout) ~= nil
+ end) ~= nil then
+ fail("Found a nested callout in the document. Please fix this issue and try again.")
+ end
+
+ tappend(calloutContents, contents)
+ tappend(calloutContents, suffix)
+
+ local callout = pandoc.Div(calloutContents, pandoc.Attr("", {"docx-callout"}))
+ return callout
end
-
- tappend(calloutContents, contents)
- tappend(calloutContents, suffix)
-
- local callout = pandoc.Div(calloutContents, pandoc.Attr("", {"docx-callout"}))
- return callout
-end
-
-function calloutDocx(node)
- node = decorate_callout_title_with_crossref(node)
+
+ node = _quarto.modules.callouts.decorate_callout_title_with_crossref(node)
local type = node.type
local appearance = node.appearance
local hasIcon = node.icon
- if appearance == constants.kCalloutAppearanceDefault then
+ if appearance == _quarto.modules.constants.kCalloutAppearanceDefault then
return calloutDocxDefault(node, type, hasIcon)
else
return calloutDocxSimple(node, type, hasIcon)
diff --git a/src/resources/filters/quarto-post/jats.lua b/src/resources/filters/quarto-post/jats.lua
index 0fd32fbf3b..8f267229df 100644
--- a/src/resources/filters/quarto-post/jats.lua
+++ b/src/resources/filters/quarto-post/jats.lua
@@ -3,12 +3,6 @@
local normalizeAuthors = require 'modules/authors'
local normalizeLicense = require 'modules/license'
-local constants = require("modules/constants")
-
-local function isCell(el)
- return el.classes:includes("cell")
-end
-
local function jatsMeta(meta)
-- inspect the meta and set flags that will aide the rendering of
-- the JATS template by providing some synthesize properties
@@ -30,15 +24,15 @@ local function jatsMeta(meta)
local hasLicense = meta[normalizeLicense.constants.license] ~= nil
local hasPermissions = hasCopyright or hasLicense
- if meta[constants.kQuartoInternal] == nil then
- meta[constants.kQuartoInternal] = {}
+ if meta[_quarto.modules.constants.kQuartoInternal] == nil then
+ meta[_quarto.modules.constants.kQuartoInternal] = {}
end
- meta[constants.kQuartoInternal][constants.kHasAuthorNotes] = hasNotes;
- meta[constants.kQuartoInternal][constants.kHasPermissions] = hasPermissions;
+ meta[_quarto.modules.constants.kQuartoInternal][_quarto.modules.constants.kHasAuthorNotes] = hasNotes;
+ meta[_quarto.modules.constants.kQuartoInternal][_quarto.modules.constants.kHasPermissions] = hasPermissions;
-- normalize keywords into tags if they're present and tags aren't
- if meta[constants.kTags] == nil and meta[constants.kKeywords] ~= nil and meta[constants.kKeywords].t == "Table" then
- meta[constants.kKeywords] = meta[constants.kTags]
+ if meta[_quarto.modules.constants.kTags] == nil and meta[_quarto.modules.constants.kKeywords] ~= nil and meta[_quarto.modules.constants.kKeywords].t == "Table" then
+ meta[_quarto.modules.constants.kKeywords] = meta[_quarto.modules.constants.kTags]
end
return meta
@@ -64,7 +58,7 @@ function unrollDiv(div, fnSkip)
end
function jatsCallout(node)
- local contents = resolveCalloutContents(node, true)
+ local contents = _quarto.modules.callouts.resolveCalloutContents(node, true)
local boxedStart = ''
if node.id and node.id ~= "" then
@@ -107,15 +101,7 @@ end
function jatsSubarticle()
if _quarto.format.isJatsOutput() then
-
- local isCodeCell = function(el)
- return not el.classes:includes('markdown')
- end
-
- local isCodeCellOutput = function(el)
- return el.classes:includes("cell-output")
- end
-
+
local ensureValidIdentifier = function(identifier)
-- Identifiers may not start with a digit, so add a prefix
-- if necessary to ensure that they're valid
@@ -160,8 +146,8 @@ function jatsSubarticle()
Div = function(div)
-- this is a notebook cell, handle it
- if isCell(div) then
- if isCodeCell(div) then
+ if _quarto.modules.classpredicates.isCell(div) then
+ if _quarto.modules.classpredicates.isCodeCell(div) then
-- if this is an executable notebook cell, walk the contents and add identifiers
-- to the outputs
@@ -177,7 +163,7 @@ function jatsSubarticle()
local outputEls = pandoc.List()
local otherEls = pandoc.List()
for i, v in ipairs(div.content) do
- if is_regular_node(v, "Div") and isCodeCellOutput(v) then
+ if is_regular_node(v, "Div") and _quarto.modules.classpredicates.isCodeCellOutput(v) then
outputEls:extend({v})
else
otherEls:extend({v})
@@ -191,26 +177,26 @@ function jatsSubarticle()
local count = 0
div = _quarto.ast.walk(div, {
Div = function(childEl)
- if (isCodeCellOutput(childEl)) then
+ if (_quarto.modules.classpredicates.isCodeCellOutput(childEl)) then
childEl.identifier = parentId .. '-output-' .. count
count = count + 1
- return renderCellOutput(childEl, constants.kNoteBookOutput)
+ return renderCellOutput(childEl, _quarto.modules.constants.kNoteBookOutput)
end
end
})
-- render the cell
- return renderCell(div, constants.kNoteBookCode)
+ return renderCell(div, _quarto.modules.constants.kNoteBookCode)
else
if #div.content == 0 then
-- eat empty markdown cells
return {}
else
-- the is a valid markdown cell, let it through
- return renderCell(div, constants.kNoteBookContent)
+ return renderCell(div, _quarto.modules.constants.kNoteBookContent)
end
end
- elseif isCodeCellOutput(div) then
+ elseif _quarto.modules.classpredicates.isCodeCellOutput(div) then
-- do nothing
else
-- Forward the identifier from a table div onto the table itself and
@@ -222,7 +208,7 @@ function jatsSubarticle()
else
-- otherwise, if this is a div, we can unroll its contents
return unrollDiv(div, function(el)
- return isCodeCellOutput(el) or isCell(el)
+ return _quarto.modules.classpredicates.isCodeCellOutput(el) or _quarto.modules.classpredicates.isCell(el)
end)
end
diff --git a/src/resources/filters/quarto-post/latex.lua b/src/resources/filters/quarto-post/latex.lua
index afe9159a71..a01d89f0c2 100644
--- a/src/resources/filters/quarto-post/latex.lua
+++ b/src/resources/filters/quarto-post/latex.lua
@@ -3,8 +3,6 @@
--
-- renders AST nodes to LaTeX
-local constants = require("modules/constants")
-
local callout_counters = {}
local function ensure_callout_counter(ref)
@@ -32,10 +30,10 @@ function latexCalloutBoxDefault(title, callout_type, icon, callout)
local borderWidth = '.15mm'
local borderRadius = '.35mm'
local leftPad = '2mm'
- local color = latexColorForType(callout_type)
- local frameColor = latexFrameColorForType(callout_type)
+ local color = _quarto.modules.callouts.latexColorForType(callout_type)
+ local frameColor = _quarto.modules.callouts.latexFrameColorForType(callout_type)
- local iconForType = iconForType(callout_type)
+ local iconForType = _quarto.modules.callouts.iconForType(callout_type)
local calloutContents = pandoc.List({});
@@ -101,8 +99,8 @@ function latexCalloutBoxSimple(title, type, icon, callout)
local borderWidth = '.15mm'
local borderRadius = '.35mm'
local leftPad = '2mm'
- local color = latexColorForType(type)
- local colorFrame = latexFrameColorForType(type)
+ local color = _quarto.modules.callouts.latexColorForType(type)
+ local colorFrame = _quarto.modules.callouts.latexFrameColorForType(type)
if title == nil then
title = ""
@@ -145,7 +143,7 @@ function latexCalloutBoxSimple(title, type, icon, callout)
local endInlines = { pandoc.RawInline('latex', '\n\\end{tcolorbox}') }
-- generate the icon and use a minipage to position it
- local iconForCat = iconForType(type)
+ local iconForCat = _quarto.modules.callouts.iconForType(type)
if icon ~= false and iconForCat ~= nil then
local iconName = '\\' .. iconForCat
local iconColSize = '5.5mm'
@@ -360,9 +358,9 @@ function render_latex()
-- generate the callout box
local callout
- if calloutAppearance == constants.kCalloutAppearanceDefault then
+ if calloutAppearance == _quarto.modules.constants.kCalloutAppearanceDefault then
if title == nil then
- title = displayName(type)
+ title = _quarto.modules.callouts.displayName(type)
else
title = pandoc.write(pandoc.Pandoc(title), 'latex')
end
diff --git a/tests/docs/smoke-all/2024/01/31/8507.docx.snapshot b/tests/docs/smoke-all/2024/01/31/8507.docx.snapshot
index fb2e0bfc4a..231b42d5c0 100644
Binary files a/tests/docs/smoke-all/2024/01/31/8507.docx.snapshot and b/tests/docs/smoke-all/2024/01/31/8507.docx.snapshot differ