From b84256cd5abcaf599a7056b9094e1ffe117efa49 Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Wed, 6 May 2026 14:06:45 +0200 Subject: [PATCH 1/3] feat: use custom html builder for widget3 --- lua/wikis/commons/Widget/Renderer.lua | 108 ++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 16 deletions(-) diff --git a/lua/wikis/commons/Widget/Renderer.lua b/lua/wikis/commons/Widget/Renderer.lua index c2674a9a91a..b59d3d5aa70 100644 --- a/lua/wikis/commons/Widget/Renderer.lua +++ b/lua/wikis/commons/Widget/Renderer.lua @@ -12,6 +12,92 @@ local Types = Lua.import('Module:Widget/Types') local Renderer = {} +-- List of HTML tags that cannot have children and do not need closing tags +local selfClosingTags = { + area = true, + base = true, + br = true, + col = true, + command = true, + embed = true, + hr = true, + img = true, + input = true, + keygen = true, + link = true, + meta = true, + param = true, + source = true, + track = true, + wbr = true, +} + +-- Basic attribute escaper (prevents quotes from breaking HTML) +local htmlencodeMap = { + ['>'] = '>', + ['<'] = '<', + ['&'] = '&', + ['"'] = '"', +} + +---@param str any +---@return string +local function escapeAttr(str) + if type(str) ~= 'string' then + str = tostring(str) + end + for char, escape in pairs(htmlencodeMap) do + str = str:gsub(char, escape) + end + return str +end + +--- Builds an HTML string from the given tag, props, and children +---@param tag string|nil +---@param props table +---@param renderedChildren string? +---@return string +local function buildHtmlString(tag, props, renderedChildren) + local buffer = { '<', tag } + + if props.classes and #props.classes > 0 then + table.insert(buffer, ' class="') + table.insert(buffer, escapeAttr(table.concat(props.classes, ' '))) + table.insert(buffer, '"') + end + + if props.css then + table.insert(buffer, ' style="') + for k, v in pairs(props.css) do + table.insert(buffer, k .. ':' .. tostring(v) .. ';') + end + table.insert(buffer, '"') + end + + if props.attr then + for k, v in pairs(props.attr) do + if type(v) == 'boolean' then + -- Boolean attributes like `disabled` or `checked` + if v then table.insert(buffer, ' ' .. k) end + else + table.insert(buffer, ' ' .. k .. '="' .. escapeAttr(v) .. '"') + end + end + end + + if selfClosingTags[tag] then + table.insert(buffer, ' />') + else + table.insert(buffer, '>') + if renderedChildren and renderedChildren ~= '' then + table.insert(buffer, renderedChildren) + end + table.insert(buffer, '') + end + + return table.concat(buffer) +end + --- Renders a Virtual Node (VNode) into a string ---@param vNode Renderable|Renderable[]|nil ---@param context Context? @@ -80,26 +166,16 @@ function Renderer.render(vNode, context) -- Handle HTML Tags if type(renderFn) == 'string' then ---@cast vNode HtmlNode - local props = vNode.props - local tagName = renderFn - local tag - if tagName == 'fragment' then - tag = mw.html.create() - else - tag = mw.html.create(tagName) - end - - if props.classes then - tag:addClass(table.concat(props.classes, ' ')) + local renderedChildren = '' + if vNode.props.children then + renderedChildren = Renderer.render(vNode.props.children, context) end - if props.css then tag:css(props.css) end - if props.attributes then tag:attr(props.attributes) end - if props.children then - tag:node(Renderer.render(props.children, context)) + if renderFn == 'fragment' then + return renderedChildren end - return tostring(tag) + return buildHtmlString(renderFn, vNode.props, renderedChildren) end -- Handle Functional Components From 89b5e73acdec9c9c039beacdc1f71e580977440e Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Wed, 6 May 2026 15:58:09 +0200 Subject: [PATCH 2/3] rename --- lua/wikis/commons/Widget/Renderer.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lua/wikis/commons/Widget/Renderer.lua b/lua/wikis/commons/Widget/Renderer.lua index b59d3d5aa70..6b344a65f9c 100644 --- a/lua/wikis/commons/Widget/Renderer.lua +++ b/lua/wikis/commons/Widget/Renderer.lua @@ -53,8 +53,8 @@ local function escapeAttr(str) end --- Builds an HTML string from the given tag, props, and children ----@param tag string|nil ----@param props table +---@param tag string +---@param props {classes?: string[], css: table, attributes: table} ---@param renderedChildren string? ---@return string local function buildHtmlString(tag, props, renderedChildren) @@ -74,8 +74,8 @@ local function buildHtmlString(tag, props, renderedChildren) table.insert(buffer, '"') end - if props.attr then - for k, v in pairs(props.attr) do + if props.attributes then + for k, v in pairs(props.attributes) do if type(v) == 'boolean' then -- Boolean attributes like `disabled` or `checked` if v then table.insert(buffer, ' ' .. k) end From 0abfab5803df405e6544eab87166e8c5f7d97a27 Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Thu, 7 May 2026 10:51:09 +0200 Subject: [PATCH 3/3] cleanup --- lua/wikis/commons/Widget/Renderer.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lua/wikis/commons/Widget/Renderer.lua b/lua/wikis/commons/Widget/Renderer.lua index 6b344a65f9c..3da066c886d 100644 --- a/lua/wikis/commons/Widget/Renderer.lua +++ b/lua/wikis/commons/Widget/Renderer.lua @@ -68,19 +68,21 @@ local function buildHtmlString(tag, props, renderedChildren) if props.css then table.insert(buffer, ' style="') - for k, v in pairs(props.css) do - table.insert(buffer, k .. ':' .. tostring(v) .. ';') + for key, value in pairs(props.css) do + table.insert(buffer, key .. ':' .. escapeAttr(tostring(value)) .. ';') end table.insert(buffer, '"') end if props.attributes then - for k, v in pairs(props.attributes) do - if type(v) == 'boolean' then + for key, value in pairs(props.attributes) do + if type(value) == 'boolean' then -- Boolean attributes like `disabled` or `checked` - if v then table.insert(buffer, ' ' .. k) end + if value == true then + table.insert(buffer, ' ' .. key) + end else - table.insert(buffer, ' ' .. k .. '="' .. escapeAttr(v) .. '"') + table.insert(buffer, ' ' .. key .. '="' .. escapeAttr(value) .. '"') end end end