-- Converts Obsidian callout blockquotes to HTML that mirrors Obsidian's -- structure more closely, including aliases and foldable callouts. local aliases = { summary = "abstract", tldr = "abstract", hint = "tip", important = "tip", check = "success", done = "success", help = "question", faq = "question", caution = "warning", attention = "warning", fail = "failure", missing = "failure", error = "danger", cite = "quote", } local supported = { note = true, abstract = true, info = true, todo = true, tip = true, success = true, question = true, warning = true, failure = true, danger = true, bug = true, example = true, quote = true, } local icons = { note = [[]], abstract = [[]], info = [[]], todo = [[]], tip = [[]], success = [[]], question = [[]], warning = [[]], failure = [[]], danger = [[]], bug = [[]], example = [[]], quote = [[]], } local function trim(text) return (text:gsub("^%s+", ""):gsub("%s+$", "")) end local function title_case(identifier) local words = {} for part in identifier:gmatch("[^%-_]+") do table.insert(words, part:sub(1, 1):upper() .. part:sub(2):lower()) end return table.concat(words, " ") end local function render_blocks_html(blocks) local doc = pandoc.Pandoc(blocks) local walked = doc:walk({ BlockQuote = BlockQuote }) return pandoc.write(walked, "html") end local function render_inlines_html(inlines) if #inlines == 0 then return "" end local html = pandoc.write(pandoc.Pandoc({ pandoc.Plain(inlines) }), "html") return trim(html) end local function collect_body_blocks(block_quote, first_block, content_start_idx) local body_blocks = pandoc.List({}) local remaining_inlines = pandoc.List({}) for i = content_start_idx, #first_block.content do remaining_inlines:insert(first_block.content[i]) end if #remaining_inlines > 0 then body_blocks:insert(pandoc.Para(remaining_inlines)) end for i = 2, #block_quote.content do body_blocks:insert(block_quote.content[i]) end return body_blocks end function BlockQuote(el) if #el.content == 0 then return el end local first_block = el.content[1] if first_block.t ~= "Para" or #first_block.content == 0 then return el end local first_elem = first_block.content[1] if not first_elem or first_elem.t ~= "Str" then return el end local raw_type, fold_state = first_elem.text:match("^%[!([^%]]+)%]([%+%-]?)$") if not raw_type then return el end local raw_type_lower = raw_type:lower() local canonical_type = aliases[raw_type_lower] or raw_type_lower if canonical_type == "sidenote" or canonical_type == "sidenote-l" then local body_blocks = collect_body_blocks(el, first_block, 2) local content_html = render_blocks_html(body_blocks) local sidenote_class = canonical_type == "sidenote-l" and "sidenote sidenote-left" or "sidenote sidenote-right" return pandoc.RawBlock("html", string.format('%s', sidenote_class, content_html)) end local title_parts = pandoc.List({}) local content_start_idx = 2 for i = 2, #first_block.content do local elem = first_block.content[i] if elem.t == "SoftBreak" or elem.t == "LineBreak" then content_start_idx = i + 1 break end title_parts:insert(elem) content_start_idx = i + 1 end local body_blocks = collect_body_blocks(el, first_block, content_start_idx) local body_html = render_blocks_html(body_blocks) local style_type = supported[canonical_type] and canonical_type or "note" local title_html = render_inlines_html(title_parts) if title_html == "" then title_html = title_case(raw_type_lower) end local icon_markup = string.format( '', icons[style_type] or icons.note ) local title_markup = string.format( '%s
%s
', icon_markup, title_html ) local content_markup = "" if trim(body_html) ~= "" then content_markup = string.format('\n
\n%s\n
', body_html) end local attributes = string.format( 'class="callout callout-%s" data-callout="%s"', style_type, raw_type_lower ) if fold_state ~= "" then attributes = attributes .. string.format(' data-callout-fold="%s"', fold_state) local open_attr = fold_state == "+" and " open" or "" return pandoc.RawBlock( "html", string.format( '
\n%s%s\n
', attributes, open_attr, title_markup, content_markup ) ) end return pandoc.RawBlock( "html", string.format( '
\n
%s
%s\n
', attributes, title_markup, content_markup ) ) end