Module:InfoboxLite

From Dune: Awakening Community Wiki
Jump to navigation Jump to search

Module:InfoboxLite

A lightweight, optimised variant of Module:Infobox.

Identical interface but with the following removed:

  • Navbar
  • Italic title
  • TemplateStyles loading
  • Nested wikitable support
  • Per-row styling parameters
  • autoheaders
  • child / subbox support

These were removed because there are currently no use cases for them on the wiki, and calling them 150~ times on content-heavy pages (such as Research) was causing Lua CPU timeouts.

Row handling

Numbered parameters are not capped. Rows are detected from the parameters actually supplied and rendered in ascending numeric order, so gaps are permitted: |data1= and |data5= render as two consecutive rows.

Duplicate indices resolve to the same row: data7 and data007 are treated as one.

List handling

List markup (* and #) is only supported for data and below.

Styling

Only box-level styling parameters are supported, e.g. bodyclass, bodystyle.

Per-row equivalents (class, rowclass, rowstyle, rowcellstyle, imagerowclass, and numbered subheaderstyle) are not supported.

Usage

Use Template:InfoboxLite in place of Template:Infobox. Most parameters are identical; see above for exceptions.


local p = {}

local common = require("Module:Common")

local ustring = mw.ustring
local html = mw.html

-- ---------- Data ----------

local CATEGORY_EMPTY_ROW_REGEX = "%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^%]]*%]%]"

-- fields that render on their own; a row exists only if one of these is supplied
local INDEXED_FIELDS = {
    subheader = true,
    image = true,
    header = true,
    data = true,
}

-- ---------- Helpers ----------

-- strip categories since category links count as non-whitespace, which can cause empty infobox fields to show up
local function hasVisibleText(s)
    if not s then return false end

    if s:find("%[%[") then s = ustring.gsub(s, CATEGORY_EMPTY_ROW_REGEX, "") end

    return s:find("%S") ~= nil
end

-- collect the numeric suffixes actually supplied for each standalone field
local function collectIndices(args)
    local found, seen = {}, {}

    for field in pairs(INDEXED_FIELDS) do
        found[field] = {}
        seen[field] = {}
    end

    for k in pairs(args) do
        local field, digits = k:match("^(%a+)(%d+)$")

        if field and INDEXED_FIELDS[field] then
            local n = tonumber(digits)

            if n and not seen[field][n] then
                seen[field][n] = true

                local list = found[field]
                list[#list + 1] = n
            end
        end
    end

    for _, list in pairs(found) do table.sort(list) end

    return found
end

-- union of header and data indices, since either alone creates a row
local function mergeIndices(a, b)
    local seen, merged = {}, {}

    for _, list in ipairs({a, b}) do
        for _, n in ipairs(list) do
            if not seen[n] then
                seen[n] = true
                merged[#merged + 1] = n
            end
        end
    end

    table.sort(merged)
    return merged
end

-- process bullet lists to ensure they render correctly
local function fixChildBoxes(s)
    if not s:find("[%*#]") then return s end

    s = ustring.gsub(s, "([\r\n][%*#;:][^\r\n]*)$", "%1\n")  -- trailing newline after a final list item
    s = ustring.gsub(s, "^([%*#;:][^\r\n]*)$", "%1\n")  -- single-line list: add trailing newline
    s = ustring.gsub(s, "^([%*#;:])", "\n%1")  -- list starts at char 1: add leading newline

    return s
end

-- ---------- Rendering ----------

local function renderAboveRow(root, args)
    local above = args.above
    if not above then return end

    root
        :tag("tr")
        :tag("th")
            :attr("colspan", "2")
            :addClass("infobox-above")
            :addClass(args.aboveclass)
            :cssText(args.abovestyle)
            :wikitext(above)
end

local function renderSubheaders(root, args, indices)
    if args.subheader and not args.subheader1 then
        args.subheader1 = args.subheader
        if indices[1] ~= 1 then table.insert(indices, 1, 1) end
    end

    local subheaderclass = args.subheaderclass
    local subheaderstyle = args.subheaderstyle

    for _, i in ipairs(indices) do
        local val = args["subheader" .. i]

        if hasVisibleText(val) then
            root
                :tag("tr")
                :tag("td")
                    :attr("colspan", "2")
                    :addClass("infobox-subheader")
                    :addClass(subheaderclass)
                    :cssText(subheaderstyle)
                    :wikitext(val)
        end
    end
end

local function renderImages(root, args, indices)
    if args.image and not args.image1 then
        args.image1 = args.image
        if indices[1] ~= 1 then table.insert(indices, 1, 1) end
    end

    if args.caption and not args.caption1 then args.caption1 = args.caption end

    local captionstyle = args.captionstyle
    local imageclass = args.imageclass
    local imagestyle = args.imagestyle

    for _, i in ipairs(indices) do
        local img = args["image" .. i]

        if hasVisibleText(img) then
            local caption = args["caption" .. i]
            local content

            if caption then
                local data = html.create()
                data:wikitext(img)
                data:tag("div")
                    :addClass("infobox-caption")
                    :cssText(captionstyle)
                    :wikitext(caption)
                content = tostring(data)
            end

            root
                :tag("tr")
                :tag("td")
                    :attr("colspan", "2")
                    :addClass("infobox-image")
                    :addClass(imageclass)
                    :cssText(imagestyle)
                    :wikitext(content or img)
        end
    end
end

local function renderRows(root, args, indices, empty_row_categories)
    local headerclass = args.headerclass
    local headerstyle = args.headerstyle
    local labelstyle  = args.labelstyle
    local datastyle   = args.datastyle

    for _, i in ipairs(indices) do
        local header = args["header" .. i]
        local data = args["data" .. i]
        local label = args["label" .. i]

        if header and header ~= "_BLANK_" then
            root
                :tag("tr")
                :tag("th")
                    :attr("colspan", "2")
                    :addClass("infobox-header")
                    :addClass(headerclass)
                    :cssText(headerstyle)
                    :wikitext(header)

        elseif hasVisibleText(data) then
            local row = root:tag("tr")

            if label then
                row
                    :tag("th")
                        :attr("scope", "row")
                        :addClass("infobox-label")
                        :cssText(labelstyle)
                        :wikitext(label)
                        :done()
            end

            row:tag("td")
                :attr("colspan", not label and "2" or nil)
                :addClass(not label and "infobox-full-data" or "infobox-data")
                :cssText(datastyle)
                :wikitext(fixChildBoxes(data))

        elseif data then empty_row_categories[#empty_row_categories + 1] = data end
    end
end

local function renderBelowRow(root, args)
    local below = args.below
    if not below then return end

    root
        :tag("tr")
        :tag("td")
            :attr("colspan", "2")
            :addClass("infobox-below")
            :addClass(args.belowclass)
            :cssText(args.belowstyle)
            :wikitext(fixChildBoxes(below))
end

local function renderEmptyRowCategories(root, empty_row_categories)
    for _, s in ipairs(empty_row_categories) do root:wikitext(s) end
end

-- ---------- Main ----------

local function _infobox(origArgs)
    local args = {}

    for k, v in pairs(origArgs) do
        v = common.trim(v)
        if v ~= "" then args[k] = v end
    end

    local indices = collectIndices(args)
    local rowIndices = mergeIndices(indices.header, indices.data)

    local empty_row_categories = {}

    local root = html.create("table")

    root
        :addClass("infobox")
        :addClass(args.bodyclass)
        :cssText(args.bodystyle)

    if args.title then
        root
            :tag("caption")
                :addClass("infobox-title")
                :addClass(args.titleclass)
                :cssText(args.titlestyle)
                :wikitext(args.title)
    end

    renderAboveRow(root, args)
    renderSubheaders(root, args, indices.subheader)
    renderImages(root, args, indices.image)
    renderRows(root, args, rowIndices, empty_row_categories)
    renderBelowRow(root, args)
    renderEmptyRowCategories(root, empty_row_categories)

    return tostring(root)
end

-- ---------- Interface ----------

function p.infobox(frame)
    local origArgs

    if frame == mw.getCurrentFrame() then origArgs = frame:getParent().args
    else origArgs = frame end

    return _infobox(origArgs)
end

function p.infoboxTemplate(frame)
    return _infobox(frame.args)
end

return p