From e46883c3c1e23476b566ac896476ac62a3f3bfce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Mon, 26 May 2025 19:34:12 +0300 Subject: [PATCH] add lists support to babycode --- data/static/style.css | 2 +- lib/babycode.lua | 87 ++++++++++++++++++++++++++++++---- sass/style.scss | 2 +- views/common/bbcode_help.etlua | 21 ++++++++ 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/data/static/style.css b/data/static/style.css index 39286de..5f205f6 100644 --- a/data/static/style.css +++ b/data/static/style.css @@ -507,7 +507,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus, select:focus height: 150px; } -ul { +ul, ol { margin: 10px 0 10px 30px; padding: 0; } diff --git a/lib/babycode.lua b/lib/babycode.lua index f76b84c..0a72104 100644 --- a/lib/babycode.lua +++ b/lib/babycode.lua @@ -1,16 +1,82 @@ local babycode = {} +local string_trim = require("lapis.util").trim + +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 get_list_items(list_body, escape_html) + list_body = list_body:gsub(" +%s*\r?\n", "
") + list_body = list_body:gsub("(%S)(\r?\n\r?\n)\r?\n*", "%1\1") + local list_items = s_split(list_body, "\1") + local lis = "" + for _, li in ipairs(list_items) do + local rendered = babycode.to_html(li, escape_html) + lis = lis .. "
  • " .. rendered .. "
  • " + end + return lis +end + ---renders babycode to html ---@param s string input babycode ---@param escape_html fun(s: string): string function that escapes html function babycode.to_html(s, escape_html) if not s or s == "" then return "" end - -- extract code blocks first and store them as placeholders + local text = escape_html(s) + -- extract code blocks and store them as placeholders -- don't want to process bbcode embedded into a code block local code_blocks = {} local inline_codes = {} - s = escape_html(s) - local text = s:gsub("%[code%](.-)%[/code%]", function(code) + text = text:gsub("%[code%](.-)%[/code%]", function(code) local is_inline = code:match("\n") == nil if is_inline then table.insert(inline_codes, code) @@ -23,10 +89,17 @@ function babycode.to_html(s, escape_html) end end) - text = text:gsub(" %s?\r?\n", "
    ") - -- normalize newlines - text = text:gsub("\r?\n\r?\n+", "
    ") + text = text:gsub("%[ul%](.-)%[/ul%]", function(list_body) + return "" + end) + text = text:gsub("%[ol%](.-)%[/ol%]", function(list_body) + return "
      " .. get_list_items(list_body, escape_html) .. "
    " + end) + -- normalize newlines, attempt #4 + text = text:gsub(" +%s*\r?\n", "
    ") + text = text:gsub("(%S)(\r?\n\r?\n)\r?\n*", "%1

    ") + local url_tags = {} -- replace `[url=https://example.com]Example[/url] tags text = text:gsub("%[url=([^%]]+)%](.-)%[/url%]", function(url, label) @@ -40,8 +113,6 @@ function babycode.to_html(s, escape_html) text = text:gsub("%[s%](.-)%[/s%]", "%1") -- these can be nested, so replace open and closed separately - -- text = text:gsub("%[quote%]", "
    ") - -- text = text:gsub("%[/quote%]", "
    ") text = text:gsub("%[(/?)quote%]", "<%1blockquote>") -- replace loose links diff --git a/sass/style.scss b/sass/style.scss index 7a4ee19..fe3255e 100644 --- a/sass/style.scss +++ b/sass/style.scss @@ -523,7 +523,7 @@ input[type="text"], input[type="password"], textarea, select { } -ul { +ul, ol { margin: 10px 0 10px 30px; padding: 0; } diff --git a/views/common/bbcode_help.etlua b/views/common/bbcode_help.etlua index e934c67..d43a1df 100644 --- a/views/common/bbcode_help.etlua +++ b/views/common/bbcode_help.etlua @@ -6,6 +6,27 @@
  • [i]italic[/i]
  • [s]strikethrough[/s]
  • [url=https://example.com]labeled URL[/url]
  • +
  • + [ul] and [ol] are unordered and ordered lists: +
    + Show list example +
    [ul]
    +item 1
    +
    +item 2
    +
    +item 3  
    +still item 3 (break line without inserting a new item by using two spaces at the end of a line)
    +[/ul]
    +
    +
  • [code]with
    line breaks[/code] will produce a code block: