Module:Infobox: Difference between revisions
From koreapedia
No edit summary |
No edit summary |
||
Line 1: | Line 1: | ||
-- Module:Infobox | -- Module:Infobox | ||
-- | -- Dependency-free, Lua-powered infobox builder. | ||
-- - Outputs HTML with CSS classes: .infobox, .infobox-title, .infobox-image, .infobox-caption, .infobox-label, .infobox-data | |||
-- - Auto-hides empty rows | |||
-- - Safe in previews (guards getParent()) | |||
-- - Optional 'order' control in wrappers: | order = Label A|Label B|Label C | |||
local p = {} | local p = {} | ||
-- ========= helpers ========= | |||
local function trim(s) | |||
if type(s) ~= "string" then return s end | |||
return mw.text.trim(s) | |||
end | |||
local function isNonEmpty(v) | local function isNonEmpty(v) | ||
Line 13: | Line 24: | ||
if not isNonEmpty(filename) then return nil end | if not isNonEmpty(filename) then return nil end | ||
size = size or "280px" | size = size or "280px" | ||
return string.format("[[File:%s|%s|center]]", | return string.format("[[File:%s|%s|center]]", trim(filename), size) | ||
end | end | ||
local function splitPipeList(s) | |||
function | -- Split a pipe-separated string into an array; ignores empty segments | ||
local | local t = {} | ||
for | if not isNonEmpty(s) then return t end | ||
for seg in tostring(s):gmatch("([^|]+)") do | |||
seg = mw.text.trim(seg) | |||
if seg ~= "" then table.insert(t, seg) end | |||
end | end | ||
return t | |||
end | |||
-- ========= core table builder ========= | |||
local function buildTable(args) | |||
local root = mw.html.create("table"):addClass("infobox") | local root = mw.html.create("table"):addClass("infobox") | ||
Line 55: | Line 70: | ||
end | end | ||
-- | -- Numbered label/data pairs | ||
for i = 1, | local maxRows = tonumber(args.maxrows) or 50 | ||
local label = args["label" .. i] | for i = 1, maxRows do | ||
local data = args["data" .. i] | local label = trim(args["label" .. i] or "") | ||
local data = trim(args["data" .. i] or "") | |||
if isNonEmpty(label) or isNonEmpty(data) then | if isNonEmpty(label) or isNonEmpty(data) then | ||
local tr = root:tag("tr") | local tr = root:tag("tr") | ||
tr:tag("th"):addClass("infobox-label"):wikitext(label or "") | tr:tag("th"):addClass("infobox-label"):wikitext(label or "") | ||
tr:tag("td"):addClass("infobox-data") :wikitext(data or "") | tr:tag("td"):addClass("infobox-data") :wikitext(data or "") | ||
end | end | ||
end | end | ||
Line 69: | Line 85: | ||
end | end | ||
-- | -- Public: direct builder; merges parent + frame args | ||
function p.build(frame) | |||
local args = {} | |||
-- parent args | |||
if type(frame.getParent) == "function" then | |||
local parent = frame:getParent() | |||
if parent and parent.args then | |||
for k, v in pairs(parent.args) do args[k] = v end | |||
end | |||
end | |||
-- direct args | |||
if frame and frame.args then | |||
for k, v in pairs(frame.args) do args[k] = v end | |||
end | |||
return buildTable(args) | |||
end | |||
-- Public: smart wrapper builder | |||
-- Wrappers pass a mapping of "Label = paramName" in frame.args, | |||
-- and end-users supply values via those paramNames. | |||
-- Optional: wrappers can set | order = Label A|Label B|Label C to control row order. | |||
function p.smart(frame) | function p.smart(frame) | ||
-- | -- Safe guards for parent | ||
local parent = nil | local parent = nil | ||
if type(frame.getParent) == "function" then | if type(frame.getParent) == "function" then | ||
Line 77: | Line 113: | ||
end | end | ||
local | local user = (parent and parent.args) or {} -- end-user provided values | ||
local map = frame.args or {} | local map = frame.args or {} -- wrapper's label->param map (plus optional 'order') | ||
-- Build numbered pairs only for non-empty values | |||
local newArgs = {} | local newArgs = {} | ||
-- Preserve order if wrapper provided it | |||
local orderList = splitPipeList(map.order) | |||
local added = 0 | |||
local function addRow(label, key) | |||
local value = user[key] | |||
if isNonEmpty(value) then | |||
added = added + 1 | |||
newArgs["label" .. added] = label | |||
newArgs["data" .. added] = value | |||
end | |||
end | |||
-- 1) Ordered labels first (if any) | |||
for _, label in ipairs(orderList) do | |||
local key = map[label] | |||
if isNonEmpty(key) then | |||
addRow(label, key) | |||
end | |||
end | |||
-- 2) Any remaining labels not covered by 'order' | |||
for label, key in pairs(map) do | for label, key in pairs(map) do | ||
-- skip control keys | |||
if | if label ~= "order" and type(label) == "string" then | ||
-- If it wasn't already added via order | |||
local already = false | |||
for _, olab in ipairs(orderList) do | |||
if olab == label then already = true; break end | |||
end | |||
if not already and isNonEmpty(key) then | |||
addRow(label, key) | |||
end | |||
end | end | ||
end | end | ||
newArgs.title | -- Pass through common header fields | ||
newArgs.image | newArgs.title = user.title or user.name or mw.title.getCurrentTitle().text | ||
newArgs.image_size | newArgs.image = user.image | ||
newArgs.caption | newArgs.image_size = user.image_size | ||
newArgs.caption = user.caption | |||
newArgs.maxrows = user.maxrows or 50 | |||
-- Build via the core function | |||
local fakeFrame = { args = newArgs } | local fakeFrame = { args = newArgs } | ||
return | return buildTable(fakeFrame) | ||
end | end | ||
return p | return p |
Revision as of 17:54, 8 October 2025
Documentation for this module may be created at Module:Infobox/doc
-- Module:Infobox
-- Dependency-free, Lua-powered infobox builder.
-- - Outputs HTML with CSS classes: .infobox, .infobox-title, .infobox-image, .infobox-caption, .infobox-label, .infobox-data
-- - Auto-hides empty rows
-- - Safe in previews (guards getParent())
-- - Optional 'order' control in wrappers: | order = Label A|Label B|Label C
local p = {}
-- ========= helpers =========
local function trim(s)
if type(s) ~= "string" then return s end
return mw.text.trim(s)
end
local function isNonEmpty(v)
if v == nil then return false end
if type(v) ~= "string" then return true end
return mw.text.trim(v) ~= ""
end
local function renderImage(filename, size)
if not isNonEmpty(filename) then return nil end
size = size or "280px"
return string.format("[[File:%s|%s|center]]", trim(filename), size)
end
local function splitPipeList(s)
-- Split a pipe-separated string into an array; ignores empty segments
local t = {}
if not isNonEmpty(s) then return t end
for seg in tostring(s):gmatch("([^|]+)") do
seg = mw.text.trim(seg)
if seg ~= "" then table.insert(t, seg) end
end
return t
end
-- ========= core table builder =========
local function buildTable(args)
local root = mw.html.create("table"):addClass("infobox")
-- Title
local title = args.title or args.name or mw.title.getCurrentTitle().text
root:tag("tr")
:tag("th")
:attr("colspan", "2")
:addClass("infobox-title")
:wikitext(title)
-- Optional image
local image = renderImage(args.image, args.image_size)
if image then
root:tag("tr")
:tag("td")
:attr("colspan", "2")
:addClass("infobox-image")
:wikitext(image)
end
-- Optional caption
if isNonEmpty(args.caption) then
root:tag("tr")
:tag("td")
:attr("colspan", "2")
:addClass("infobox-caption")
:wikitext(args.caption)
end
-- Numbered label/data pairs
local maxRows = tonumber(args.maxrows) or 50
for i = 1, maxRows do
local label = trim(args["label" .. i] or "")
local data = trim(args["data" .. i] or "")
if isNonEmpty(label) or isNonEmpty(data) then
local tr = root:tag("tr")
tr:tag("th"):addClass("infobox-label"):wikitext(label or "")
tr:tag("td"):addClass("infobox-data") :wikitext(data or "")
end
end
return tostring(root)
end
-- Public: direct builder; merges parent + frame args
function p.build(frame)
local args = {}
-- parent args
if type(frame.getParent) == "function" then
local parent = frame:getParent()
if parent and parent.args then
for k, v in pairs(parent.args) do args[k] = v end
end
end
-- direct args
if frame and frame.args then
for k, v in pairs(frame.args) do args[k] = v end
end
return buildTable(args)
end
-- Public: smart wrapper builder
-- Wrappers pass a mapping of "Label = paramName" in frame.args,
-- and end-users supply values via those paramNames.
-- Optional: wrappers can set | order = Label A|Label B|Label C to control row order.
function p.smart(frame)
-- Safe guards for parent
local parent = nil
if type(frame.getParent) == "function" then
parent = frame:getParent()
end
local user = (parent and parent.args) or {} -- end-user provided values
local map = frame.args or {} -- wrapper's label->param map (plus optional 'order')
-- Build numbered pairs only for non-empty values
local newArgs = {}
-- Preserve order if wrapper provided it
local orderList = splitPipeList(map.order)
local added = 0
local function addRow(label, key)
local value = user[key]
if isNonEmpty(value) then
added = added + 1
newArgs["label" .. added] = label
newArgs["data" .. added] = value
end
end
-- 1) Ordered labels first (if any)
for _, label in ipairs(orderList) do
local key = map[label]
if isNonEmpty(key) then
addRow(label, key)
end
end
-- 2) Any remaining labels not covered by 'order'
for label, key in pairs(map) do
-- skip control keys
if label ~= "order" and type(label) == "string" then
-- If it wasn't already added via order
local already = false
for _, olab in ipairs(orderList) do
if olab == label then already = true; break end
end
if not already and isNonEmpty(key) then
addRow(label, key)
end
end
end
-- Pass through common header fields
newArgs.title = user.title or user.name or mw.title.getCurrentTitle().text
newArgs.image = user.image
newArgs.image_size = user.image_size
newArgs.caption = user.caption
newArgs.maxrows = user.maxrows or 50
-- Build via the core function
local fakeFrame = { args = newArgs }
return buildTable(fakeFrame)
end
return p