porom/lib/babycode.lua

151 lines
4.2 KiB
Lua

local babycode = {}
local string_trim = require("lapis.util").trim
local emoji = require("lib.babycode-emoji")
local Parser = require("lib.babycode-parser")
local function s_split(s, delimiter, max_matches, trim, allow_empty)
local result = {}
if s == "" then
return result
end
trim = trim == nil and true or trim
local tr = function(subj)
if trim then return string_trim(subj) else return subj end
end
max_matches = max_matches or -1
allow_empty = allow_empty == nil and true or allow_empty
if delimiter == "" then
for i=1, #s do
local c = s:sub(i, 1)
if allow_empty or c ~= "" then
table.insert(result, c)
if max_matches > 0 and #result == max_matches then
break
end
end
end
return result
end
local current_pos = 1
local delim_len = #delimiter
while true do
if max_matches > 0 and #result >= max_matches then
break
end
---@diagnostic disable-next-line: param-type-mismatch
local start_pos, end_pos = s:find(delimiter, current_pos, true)
if not start_pos then
break
end
local substr = s:sub(current_pos, start_pos - 1)
if allow_empty or substr ~= "" then
table.insert(result, tr(substr))
end
current_pos = end_pos + 1
end
local substr = s:sub(current_pos)
if allow_empty or substr ~= "" then
table.insert(result, tr(substr))
end
return result
end
local function list(tag, children)
local list_body = children:gsub(" +\n", "<br>"):gsub("\n\n+", "\1")
local list_items = s_split(list_body, "\1")
local lis = ""
for _, li in ipairs(list_items) do
lis = lis .. "<li>" .. li .. "</li>"
end
return "<" .. tag .. ">" .. lis .. "</" .. tag .. ">"
end
local tags = {
b = "<strong>$S</strong>",
i = "<em>$S</em>",
s = "<del>$S</del>",
img = "<div class=\"post-img-container\"><img class=\"block-img\" src=$A alt=%S></div>",
url = "<a href=\"$A\">$S</a>",
quote = "<blockquote>$S</blockquote>",
code = function(children)
local is_inline = children:match("\n") == nil
if is_inline then
return "<code class=\"inline-code\">" .. children .. "</code>"
else
local t = string_trim(children)
local button = ("<button type=button class=\"copy-code\" value=\"%s\">Copy</button>"):format(t)
return "<pre><span class=\"copy-code-container\">"..button.."</span><code>"..t.."</code></pre>"
end
end,
ul = function(children)
return list("ul", children)
end,
ol = function(children)
return list("ol", children)
end,
}
local text_only = {
code = true,
}
---renders babycode to html
---@param s string input babycode
---@param html_escape fun(s: string): string function to escape html
function babycode.to_html(s, html_escape)
-- normalize line ending chars
local subj = string_trim(html_escape(s)):gsub("\r\n", "\n"):gsub("\r", "\n")
local parser = Parser.new(subj)
parser.valid_bbcode_tags = tags
parser.valid_emotes = emoji
parser.bbcode_tags_only_text_children = text_only
local elements = parser:parse()
local out = ""
local function fold(element, nobr)
if type(element) == "string" then
if nobr then
return element
end
return element:gsub(" +\n", "<br>"):gsub("\n\n+", "<br><br>")
end
if element.type == "bbcode" then
local c = ""
for _, child in ipairs(element.children) do
local _nobr = element.name == "code" or element.name == "ul" or element.name == "ol"
c = c .. fold(child, _nobr)
end
local res = ""
if type(tags[element.name]) == "string" then
res = (tags[element.name]):gsub("%$S", c)
if element.attribute then
res = res:gsub("%$A", element.attribute)
end
return res
elseif type(tags[element.name]) == "function" then
res = tags[element.name](c, element.attribute)
end
return res
elseif element.type == "link" then
return "<a href=\""..element.url.."\">"..element.url.."</a>"
elseif element.type == "emote" then
return emoji[element.name]
elseif element.type == "ruler" then
return "<hr>"
end
end
for _, e in ipairs(elements) do
out = out .. fold(e, false)
end
return out
end
return babycode