Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Documentation for this module may be created at Module:Infobox/doc

-- =============================================================================
-- Module:Infobox v5 — fully data-driven, deterministic order.
--
-- Editors NEVER edit this file to add a new field. Any parameter you pass
-- to {{Infobox|...}} that isn't one of the small set of reserved keys will
-- render as a labelled row. The parameter name becomes the row label.
--
-- ROW ORDER:
--   * By default, rows render in alphabetical order by parameter name.
--   * To control order, set `order = Race, Alignment, Status, …`. Listed
--     fields render first in that order; anything else renders alphabetically
--     after them.
--
-- RESERVED KEYS (case-insensitive — these go to specific UI slots, not rows):
--     type       — category chip above the title
--     name       — the big title (defaults to page name)
--     subtitle   — italic line under the title
--     image      — image filename (no File: prefix)
--     caption    — caption under the image
--     order      — comma-separated list of field names to render first
--
-- Everything else is a labelled row.
-- =============================================================================

local p = {}

local RESERVED = {
    ['type']     = true,
    ['name']     = true,
    ['subtitle'] = true,
    ['image']    = true,
    ['caption']  = true,
    ['order']    = true,
}

local function trim( s )
    if s == nil then return nil end
    s = mw.text.trim( tostring( s ) )
    if s == "" then return nil end
    return s
end

-- Turn a parameter name into a display label:
--   race            -> Race
--   first_appeared  -> First appeared       (underscores become spaces)
--   First appeared  -> First appeared       (already nicely cased)
--   voiced-by       -> Voiced by            (dashes become spaces)
-- Only the first letter is capitalised; later words keep their case.
local function formatLabel( key )
    local s = tostring( key )
    s = ( s:gsub( '[_%-]', ' ' ) )
    if #s > 0 then
        s = s:sub( 1, 1 ):upper() .. s:sub( 2 )
    end
    return s
end

-- Split a comma-separated "order" string into an array of trimmed names.
local function parseOrder( raw )
    local list = {}
    if raw then
        for piece in string.gmatch( raw, '([^,]+)' ) do
            local t = trim( piece )
            if t then table.insert( list, t ) end
        end
    end
    return list
end

function p.main( frame )
    local parent = frame:getParent() or frame
    local args = parent.args or {}

    -- ---- Reserved identity fields
    local typeRaw   = trim( args.type )
    local name      = trim( args.name )     or mw.title.getCurrentTitle().text
    local subtitle  = trim( args.subtitle )
    local image     = trim( args.image )
    local caption   = trim( args.caption )
    local orderList = parseOrder( trim( args.order ) )

    -- ---- Root element + identity slots
    local root = mw.html.create( 'div' ):addClass( 'fable-infobox' )
    if typeRaw then
        root:attr( 'data-infobox-type', string.lower( typeRaw ) )
        root:tag( 'div' ):addClass( 'fable-infobox__chip' )
            :wikitext( formatLabel( typeRaw ) )
    end

    root:tag( 'div' ):addClass( 'fable-infobox__title' ):wikitext( name )

    if subtitle then
        root:tag( 'div' ):addClass( 'fable-infobox__subtitle' ):wikitext( subtitle )
    end

    if image then
        local w = root:tag( 'div' ):addClass( 'fable-infobox__image' )
        w:wikitext( string.format( '[[File:%s|frameless|300x300px|center]]', image ) )
        if caption then
            w:tag( 'div' ):addClass( 'fable-infobox__caption' ):wikitext( caption )
        end
    end

    -- ---- Collect every non-reserved, non-empty arg into a key list.
    -- parent:argumentPairs() returns args in unspecified order, so we
    -- gather first, then sort, so output is deterministic.
    local nonReserved = {}
    if parent.argumentPairs then
        for key, value in parent:argumentPairs() do
            if type( key ) == 'string' and not RESERVED[ key:lower() ] then
                local v = trim( value )
                if v then
                    nonReserved[ key ] = v
                end
            end
        end
    end

    -- ---- Build final render order: explicit order list first, then the
    -- remainder alphabetically.
    local rendered = {}
    local seen = {}
    for _, k in ipairs( orderList ) do
        if nonReserved[ k ] and not seen[ k ] then
            table.insert( rendered, k )
            seen[ k ] = true
        end
    end
    local rest = {}
    for k in pairs( nonReserved ) do
        if not seen[ k ] then table.insert( rest, k ) end
    end
    table.sort( rest, function( a, b ) return a:lower() < b:lower() end )
    for _, k in ipairs( rest ) do table.insert( rendered, k ) end

    -- ---- Render rows in the final order.
    local body = root:tag( 'div' ):addClass( 'fable-infobox__rows' )
    for _, key in ipairs( rendered ) do
        local v = nonReserved[ key ]
        local r = body:tag( 'div' ):addClass( 'fable-infobox__row' )
        r:tag( 'div' ):addClass( 'fable-infobox__label' ):wikitext( formatLabel( key ) )
        r:tag( 'div' ):addClass( 'fable-infobox__value' ):wikitext( v )
    end

    local styles = frame:extensionTag{
        name = 'templatestyles',
        args = { src = 'Template:Infobox/styles.css' },
    }
    return styles .. tostring( root )
end

return p