132 lines
4.1 KiB
Lua
132 lines
4.1 KiB
Lua
|
|
--[[
|
|||
|
|
callouts.lua – Pandoc filter for the info boxes written by epub2md (--boxes callout).
|
|||
|
|
|
|||
|
|
epub2md writes boxes as GitHub alerts / Obsidian callouts:
|
|||
|
|
|
|||
|
|
> [!TIP]
|
|||
|
|
> **Title**
|
|||
|
|
>
|
|||
|
|
> Text …
|
|||
|
|
|
|||
|
|
Pandoc < 3.1.10 does not know this syntax and prints "[!TIP]" literally. This filter turns such
|
|||
|
|
block quotes into real boxes:
|
|||
|
|
|
|||
|
|
* HTML, EPUB: <div class="callout callout-tip"> with a bold label and a small stylesheet
|
|||
|
|
* other formats (LaTeX/PDF, DOCX, ODT …): block quote with a bold label "Tipp: Title"
|
|||
|
|
|
|||
|
|
It also handles <div class="callout …"> coming back from HTML (md -> html -> pdf route) and styles
|
|||
|
|
the <div class="box …"> blocks of --boxes html.
|
|||
|
|
|
|||
|
|
Usage:
|
|||
|
|
pandoc buch.md -s --lua-filter /pfad/zu/epub2md/pandoc/callouts.lua -o buch.html
|
|||
|
|
]]
|
|||
|
|
|
|||
|
|
local LABELS = {
|
|||
|
|
de = { NOTE = "Hinweis", TIP = "Tipp", IMPORTANT = "Wichtig", WARNING = "Warnung", CAUTION = "Vorsicht" },
|
|||
|
|
en = { NOTE = "Note", TIP = "Tip", IMPORTANT = "Important", WARNING = "Warning", CAUTION = "Caution" },
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
local CSS = [[
|
|||
|
|
<style>
|
|||
|
|
.callout, .box { border: 1px solid #c8ccd2; border-left: 0.35em solid #4a7bd0; border-radius: 0.3em;
|
|||
|
|
background: #f5f8fc; padding: 0.4em 1em; margin: 1em 0; }
|
|||
|
|
.callout > p:first-child { margin-top: 0.4em; }
|
|||
|
|
.callout-tip { border-left-color: #2e9a4f; background: #f3faf5; }
|
|||
|
|
.callout-important { border-left-color: #8250df; background: #f8f5fe; }
|
|||
|
|
.callout-warning { border-left-color: #c58a00; background: #fffaf0; }
|
|||
|
|
.callout-caution { border-left-color: #cf222e; background: #fff5f5; }
|
|||
|
|
</style>
|
|||
|
|
]]
|
|||
|
|
|
|||
|
|
local lang = "de"
|
|||
|
|
|
|||
|
|
local function is_html()
|
|||
|
|
return FORMAT:match("html") ~= nil or FORMAT:match("epub") ~= nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local function label_for(kind)
|
|||
|
|
local labels = LABELS[lang] or LABELS.de
|
|||
|
|
return labels[kind] or labels.NOTE
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local function drop_leading_breaks(inlines)
|
|||
|
|
while #inlines > 0 and (inlines[1].t == "SoftBreak" or inlines[1].t == "LineBreak" or inlines[1].t == "Space") do
|
|||
|
|
inlines:remove(1)
|
|||
|
|
end
|
|||
|
|
return inlines
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Build the box from its kind ("TIP" …), optional title inlines and body blocks.
|
|||
|
|
local function make_box(kind, title, body)
|
|||
|
|
local head = pandoc.Inlines({ pandoc.Str(label_for(kind)) })
|
|||
|
|
if title and #title > 0 then
|
|||
|
|
head:insert(pandoc.Str(":"))
|
|||
|
|
head:insert(pandoc.Space())
|
|||
|
|
head:extend(title)
|
|||
|
|
end
|
|||
|
|
local blocks = pandoc.Blocks({ pandoc.Para({ pandoc.Strong(head) }) })
|
|||
|
|
blocks:extend(body)
|
|||
|
|
if is_html() then
|
|||
|
|
return pandoc.Div(blocks, pandoc.Attr("", { "callout", "callout-" .. kind:lower() }))
|
|||
|
|
end
|
|||
|
|
return pandoc.BlockQuote(blocks)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local function Meta(meta)
|
|||
|
|
if meta.lang then
|
|||
|
|
local l = pandoc.utils.stringify(meta.lang):lower():sub(1, 2)
|
|||
|
|
if LABELS[l] then
|
|||
|
|
lang = l
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
if is_html() then
|
|||
|
|
local includes = meta["header-includes"]
|
|||
|
|
if includes == nil then
|
|||
|
|
includes = pandoc.MetaList({})
|
|||
|
|
elseif includes.t ~= "MetaList" then
|
|||
|
|
includes = pandoc.MetaList({ includes })
|
|||
|
|
end
|
|||
|
|
includes:insert(pandoc.MetaBlocks({ pandoc.RawBlock("html", CSS) }))
|
|||
|
|
meta["header-includes"] = includes
|
|||
|
|
end
|
|||
|
|
return meta
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local function BlockQuote(quote)
|
|||
|
|
local first = quote.content[1]
|
|||
|
|
if not first or first.t ~= "Para" or #first.content == 0 or first.content[1].t ~= "Str" then
|
|||
|
|
return nil
|
|||
|
|
end
|
|||
|
|
local kind = first.content[1].text:match("^%[!(%a+)%]$")
|
|||
|
|
if not kind then
|
|||
|
|
return nil
|
|||
|
|
end
|
|||
|
|
kind = kind:upper()
|
|||
|
|
local rest = drop_leading_breaks(first.content:clone())
|
|||
|
|
rest:remove(1)
|
|||
|
|
rest = drop_leading_breaks(rest)
|
|||
|
|
local body = pandoc.Blocks({})
|
|||
|
|
for i = 2, #quote.content do
|
|||
|
|
body:insert(quote.content[i])
|
|||
|
|
end
|
|||
|
|
local title = nil
|
|||
|
|
if #rest == 1 and rest[1].t == "Strong" then
|
|||
|
|
title = rest[1].content -- "> **Title**" line
|
|||
|
|
elseif #rest > 0 then
|
|||
|
|
body:insert(1, pandoc.Para(rest)) -- text directly after the marker
|
|||
|
|
end
|
|||
|
|
return make_box(kind, title, body)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- <div class="callout callout-tip"> read back from HTML: keep it as a box in non-HTML formats.
|
|||
|
|
local function Div(div)
|
|||
|
|
if not div.classes:includes("callout") or is_html() then
|
|||
|
|
return nil
|
|||
|
|
end
|
|||
|
|
return pandoc.BlockQuote(div.content)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
{ Meta = Meta },
|
|||
|
|
{ BlockQuote = BlockQuote, Div = Div },
|
|||
|
|
}
|