FANDOM


-- <nowiki>
local NBB = {}
 
-- Constants
local EXPANDED, COLLAPSED = 1, 2
 
-- Easy access to all arguments
local arguments = {}
 
-- Invokable - Navbox creation
function NBB.create(frame)
    if frame then arguments = getArguments(frame) end
    if type(NBB.hlist) == 'boolean' then NBB.hlist = NBB.hlist and 'hlist' or '' end
    if type(NBB.vlist) == 'boolean' then NBB.vlist = NBB.vlist and 'vlist' or '' end
 
    local main = prepMain()
    local sections = prepSections(main)
 
    local navbox = ''
    if #sections.list > 0 then navbox = makeNavbox(main, sections) end
 
    return navbox
end
-- Invokable - Return parameter documentation
function NBB.documentation(frame)
    local docs
    if not pcall(function () -- Try specified lang code
        local lang = mw.ustring.lower(mw.text.trim(tostring(frame.args[1])))
        docs = require('Dev:NavboxBuilder/doc/' .. lang)
    end) then pcall(function () -- Then wiki lang
        local lang = mw.getContentLanguage():getCode()
        docs = require('Dev:NavboxBuilder/doc/' .. lang)
    end) end
    if not docs then -- Then English
        docs = require('Dev:NavboxBuilder/doc/en') -- Then default
    end
    for k,v in pairs(NBB.params) do
        v = mw.ustring.gsub(v, '#', tostring(NBB.n), 1)
        v = mw.ustring.gsub(v, '#', tostring(NBB.m), 1)
        docs = mw.ustring.gsub(docs, '%%'..k..'%%', v)
    end
    docs = mw.ustring.gsub(docs, '%%n%%', NBB.n)
    docs = mw.ustring.gsub(docs, '%%m%%', NBB.m)
 
    return docs
end
 
-- Customizing default parameters
function NBB.changeParameters(params)
    if type(params) == 'table' then
        for k,v in pairs(params) do
            if NBB.params[k] then
                if k:sub(1, 6) == 'value_' then v = mw.ustring.lower(mw.text.trim(tostring(v) or '')) end
                NBB.params[k] = v
            end
        end
    end
    return NBB
end
 
-- Default parameter names
NBB.params = {
    -- Settings
    links = 'Links',
    state = 'State',
 
    -- Fields
    title = 'Title',
    above = 'Above',
    below = 'Below',
    limage = 'Left image',
    rimage = 'Right image',
 
    -- Sections
    header_n = 'Header #',
    layout_n = 'Layout #',
    state_n = 'State #',
    header_state = 'Header state',
 
    -- Table layout
    limage_n = 'Left image #',
    rimage_n = 'Right image #',
 
    -- Horizontal layout
    perrow_n = 'Per row #',
    span_n = 'Span #',
 
    -- Groups
    group_n = 'Group #',
    group_n_m = 'Group #.#',
    list_n = 'List #',
    list_n_m = 'List #.#',
 
    -- CSS
    navbox_class = 'Navbox class',
    navbox_style = 'Navbox style',
    title_class = 'Title class',
    title_style = 'Title style',
    base_class = 'Base class',
    base_style = 'Base style',
    above_class = 'Above class',
    above_style = 'Above style',
    below_class = 'Below class',
    below_style = 'Below style',
    image_class = 'Image class',
    image_style = 'Image style',
    limage_class = 'Left image class',
    limage_style = 'Left image style',
    rimage_class = 'Right image class',
    rimage_style = 'Right image style',
    header_class = 'Header class',
    header_style = 'Header style',
    header_n_class = 'Header # class',
    header_n_style = 'Header # style',
    limage_n_class = 'Left image # class',
    limage_n_style = 'Left image # style',
    rimage_n_class = 'Right image # class',
    rimage_n_style = 'Right image # style',
    group_class = 'Group class',
    group_style = 'Group style',
    subgroup_class = 'Subgroup class',
    subgroup_style = 'Subgroup style',
    group_n_class = 'Group # class',
    group_n_style = 'Group # style',
    group_n_m_class = 'Group #.# class',
    group_n_m_style = 'Group #.# style',
    list_class = 'List class',
    list_style = 'List style',
    list_n_class = 'List # class',
    list_n_style = 'List # style',
    list_n_m_class = 'List #.# class',
    list_n_m_style = 'List #.# style',
 
    -- Values
    value_expanded = 'expanded',
    value_collapsed = 'collapsed',
    value_table_layout = 'table',
    value_horizontal_layout = 'horizontal',
}
 
-- How n's and m's are displayed in documentations
NBB.n = '<span class="accent">n</span>'
NBB.m = '<span class="accent">m</span>'
 
-- Class names for horizontal and vertical lists
NBB.hlist = 'hlist'
NBB.vlist = 'vlist'
 
-- Cleanup of arguments from frame and its parent
function getArguments(frame, useFrame, useParent)
    local args = {
        keys = {},
        frame = {},
        parent = {},
    }
    if frame and frame.args then
        local keys = {}
        if useFrame or useFrame == nil then
            for k,v in pairs(frame.args) do
                if type(k) == 'string' then k = mw.text.trim(k) end
                if type(v) == 'string' then v = mw.text.trim(v) end
                keys[k] = true
                args.frame[k] = v
            end
        end
        if frame.getParent and (useParent or useParent == nil) then
            local parent = frame:getParent()
            for k,v in pairs(parent.args) do
                if type(k) == 'string' then k = mw.text.trim(k) end
                if type(v) == 'string' then v = mw.text.trim(v) end
                keys[k] = true
                args.parent[k] = v
            end
        end
        for k,v in pairs(keys) do table.insert(args.keys, k) end
    end
    return args
end
 
-- Returns value of a customized parameter
function getValue(key, n, m)
    local param = NBB.params[key]
    n, m = tonumber(n), tonumber(m)
    if n then param = mw.ustring.gsub(param, '#', tostring(n), 1) end
    if m then param = mw.ustring.gsub(param, '#', tostring(m), 1) end
 
    local val = arguments.parent[param] or arguments.frame[param]
    if val and val ~= '' then return val, arguments.frame[param], arguments.parent[param] end
    return nil
end
 
-- Checks the input for customized values
function checkValue(val, check)
    val = mw.ustring.lower(mw.text.trim(tostring(val) or ''))
    if type(check) == 'table' then
        for k,v in pairs(check) do
            if NBB.params['value_' .. k] then
                local test = NBB.params['value_' .. k]
                if val == test then return v end
            end
        end
    else
        check = tostring(check)
        if NBB.params['value_' .. check] then
            local test = NBB.params['value_' .. check]
            if val == test then return true end
        end
    end
    return nil
end
 
-- Returns a list of values for parameters with variables
function getList(...)
    local lists = {}
    local patterns = {}
    for i,v in ipairs(arg) do
        if v then
            lists[i] = {}
            patterns[i] = '^'..mw.ustring.gsub(NBB.params[v], '#', '([0-9]+)')..'$'
        end
    end
    for i,k in ipairs(arguments.keys) do
        local v = arguments.parent[k] or arguments.frame[k]
        if v and v ~= '' then
            for i,pattern in ipairs(patterns) do
                local n, m = mw.ustring.match(k, pattern)
                if m then
                    if not lists[i][tonumber(n)] then lists[i][tonumber(n)] = {} end
                    lists[i][tonumber(n)][tonumber(m)] = v
                    break
                elseif n then
                    lists[i][tonumber(n)] = v
                    break
                end
            end
        end
    end
    return unpack(lists)
end
 
-- Determine which section the row belongs to
function getSectionNo(test, list)
    for i=2,#list do
        if test < list[i] then return list[i-1] end
    end
    return list[#list] or nil
end
 
-- Reads parameters and prepares an object with settings for the navbox
function prepMain()
    local main = {}
 
    main.title = getValue('title')
    if main.title then
        main.links = getValue('links')
        main.state = checkValue(getValue('state'), {['expanded'] = EXPANDED, ['collapsed'] = COLLAPSED})
    end
 
    main.above = getValue('above')
    main.below = getValue('below')
    main.limage = getValue('limage')
    main.rimage = getValue('rimage')
    return main
end
 
-- Reads parameters and prepares objects with necessary settings for each section
function prepSections(main)
    local sections = {[0] = { rows = {} }}
    local headers, layouts, lists, sublists = getList('header_n', 'layout_n', 'list_n', 'list_n_m')
 
    for k,v in pairs(headers) do
        sections[k] = { header = v, rows = {} }
    end
    for k,v in pairs(layouts) do
        if not sections[k] then
            sections[k] = { layout = v, rows = {} }
        end
    end
    local numbers = getKeys(sections, true)
 
    for k,v in pairs(lists) do
        local sec = getSectionNo(k, numbers)
        sections[sec].rows[k] = { list = v, group = getValue('group_n', k) }
    end
    for k,list in pairs(sublists) do
        local sec = getSectionNo(k, numbers)
        local obj = { rows = {}, group = getValue('group_n', k) }
        for l,v in pairs(list) do
            obj.rows[l] = { list = v, group = getValue('group_n_m', k, l) }
        end
        sections[sec].rows[k] = obj
    end
    for _,v in ipairs(numbers) do
        local rows = getKeys(sections[v].rows)
        if #rows > 0 then
            sections[v].state = checkValue(getValue('state_n', v), {['expanded'] = EXPANDED, ['collapsed'] = COLLAPSED})
                                or checkValue(getValue('header_state'), {['expanded'] = EXPANDED, ['collapsed'] = COLLAPSED})
            sections[v].layout = getValue('layout_n', v) or 'table'
        else
            sections[v] = nil
        end
    end
    sections.list = getKeys(sections, true)
    return sections
end
 
-- Returns a list of keys in a table
function getKeys(tab, sorted)
    local keys = {}
    if not tab then return keys end
    for k,v in pairs(tab) do
        table.insert(keys, k)
    end
    if sorted then table.sort(keys) end
    return keys
end
 
-- Applies styles and classes from parameters to the element
function applyCSS(elem, ...)
    if not elem then return nil end
    local classes, styles = {}, {}
    for i,v in ipairs(arg) do
        local c, cP, cF, s, sP, sF
        if type(v) == 'table' then
            local key = v[1]
            v[1] = key .. '_class'
            c, cF, cP = getValue(unpack(v))
            v[1] = key .. '_style'
            s, sF, sP = getValue(unpack(v))
        else
            v = tostring(v)
            c, cF, cP = getValue(v .. '_class')
            s, sF, sP = getValue(v .. '_style')
        end
        if c then
            table.insert(classes, cF)
            table.insert(classes, cP)
        end
        if s then
            table.insert(styles, sF)
            table.insert(styles, sP)
        end
    end
    if #classes > 0 then
        classes = mw.ustring.gsub(table.concat(classes, ' '), '  +', ' ')
        elem:addClass(classes)
        end
    if #styles > 0 then
        styles = mw.ustring.gsub(table.concat(styles, ';'), ';;+', ';')
        styles = mw.ustring.gsub(styles, ';$', '')
        elem:cssText(styles)
    end
    return elem
end
 
-- Makes an element collapsible
function collapsible(elem, header, state)
    if state then
        elem:addClass('mw-collapsible')
        if state == COLLAPSED then elem:addClass('mw-collapsed') end
        if header then header:addClass('mw-collapsible-toggle') end
        return true
    end
    return false
end
 
-- Creates a navbox with prepared settings
function makeNavbox(main, sections)
    -- Structure
    local box, title, wrapper, tab, row, links, above, below, limage, rimage, content
 
    box = mw.html.create('div')
    if main.title then
        title = box:tag('div')
        if main.links then links = title:tag('div'):wikitext(main.links .. ' ') end
        title:tag('span'):addClass('navbox-title-text'):wikitext(main.title)
    end
 
    wrapper = box:tag('div')
    tab = wrapper:tag('table')
 
    if main.above then
        above = tab:tag('tr'):tag('td'):newline():wikitext(main.above)
        above:done():newline()
    end
    row = tab:tag('tr')
    if main.limage then
        limage = row:tag('td'):newline():wikitext(main.limage)
        limage:done():newline()
    end
 
    content = row:tag('td')
    for i,v in ipairs(sections.list) do content:node(makeSection(sections[v], v)) end
    if #content.nodes == 0 then return nil end
 
    if main.rimage then
        rimage = row:tag('td'):newline():wikitext(main.rimage)
        rimage:done():newline()
    end
    if main.below then
        below = tab:tag('tr'):tag('td'):newline():wikitext(main.below)
        below:done():newline()
    end
 
    -- Appearance
    box:addClass('navbox')
    applyCSS(box, 'navbox')
    wrapper:addClass('navbox-table-wrapper')
    tab:addClass('navbox-table')
    content:addClass('navbox-content')
 
    if title then
        title:addClass('navbox-title')
        applyCSS(title, 'title')
 
        if collapsible(box, title, main.state) then wrapper:addClass('mw-collapsible-content') end
    end
    if links then links:addClass('navbox-template-links') end
    if above then
        above:addClass('navbox-above navbox-base navbox-padding')
        applyCSS(above, 'base', 'above')
    end
    if below then
        below:addClass('navbox-below navbox-base navbox-padding')
        applyCSS(below, 'base', 'below')
    end
    if limage then
        limage:addClass('navbox-image')
        applyCSS(limage, 'image', 'limage')
    end
    if rimage then
        rimage:addClass('navbox-image')
        applyCSS(rimage, 'image', 'rimage')
    end
 
    if limage or rimage then
        local cols = 1 + (limage and 1 or 0) + (rimage and 1 or 0)
        if cols > 1 then
            if above then above:attr('colspan', cols) end
            if below then below:attr('colspan', cols) end
        end
    end
 
    return box
end
 
-- Creates a single section
function makeSection(section, no)
    -- Structure
    local sec, header, wrapper
 
    sec = mw.html.create('div')
 
    if section.header then header = sec:tag('div'):wikitext(section.header) end
    wrapper = sec:tag('div')
 
    -- Appearance
    sec:addClass('navbox-section')
    wrapper:addClass('navbox-section-wrapper')
    if header then
        header:addClass('navbox-header navbox-base')
        if collapsible(sec, header, section.state) then wrapper:addClass('mw-collapsible-content') end
        applyCSS(header, 'base', 'header', {'header_n', no})
    end
 
    -- Layout
    local layout
    for k,v in pairs(NBB.formats) do
        if checkValue(section.layout, k .. '_layout') then
            layout = k
            break
        else
            if k == section.layout then
                layout = k
                break
            end
        end
    end
    layout = layout or 'table'
    section.layout = layout
 
    local res = NBB.formats[layout](section, no, wrapper)
 
    if res == nil then return nil end
 
    wrapper:node(res)
    wrapper:addClass('navbox-' .. layout .. '-layout')
 
    return sec
end
 
NBB.formats = {}
 
NBB.formats.table = function(section, no)
    local lists, deep, count, keys = {}, false, 0, getKeys(section.rows, true)
    if #keys == 0 then return nil end
 
    -- Structure
    local tab, limage, rimage
 
    section.limage = getValue('limage_n', no)
    section.rimage = getValue('rimage_n', no)
 
    tab = mw.html.create('table')
    for i1,v1 in ipairs(keys) do
        count = count + 1
        local row1 = section.rows[v1]
 
        -- Structure
        local tr, group, list
 
        tr = tab:tag('tr'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd'))
        if i1 == 1 and section.limage then
            limage = tr:tag('td'):newline():wikitext(section.limage)
            limage:done():newline()
        end
        if row1.group then group = tr:tag('th'):wikitext(row1.group) end
 
        local keys = getKeys(row1.rows, true)
        if #keys > 0 then
            for i2,v2 in ipairs(keys) do
                local row2 = row1.rows[v2]
 
                -- Structure
                local tr, group, list = tr
 
                if i2 > 1 then
                    count = count + 1
                    tr = tab:tag('tr'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd'))
                end
                if row2.group then
                    group = tr:tag('th'):wikitext(row2.group)
                    deep = true
                end
 
                list = tr:tag('td'):newline():wikitext(row2.list)
                list:done():newline()
 
                -- Appearance
                list:addClass('navbox-list navbox-padding'):addClass(NBB.hlist or '')
                applyCSS(list, 'list', {'list_n_m', v1, v2})
                if not row2.group then list:attr('colspan', 2) end
                if not row1.group then table.insert(lists, list) end
 
                if group then
                    group:addClass('navbox-subgroup navbox-base navbox-padding')
                    applyCSS(group, 'base', 'subgroup', {'group_n_m', v1, v2})
                end
            end
        else
            -- Structure
            list = tr:tag('td'):newline():wikitext(row1.list)
            list:done():newline()
            table.insert(lists, list)
 
            -- Appearance
            list:addClass('navbox-list navbox-padding'):addClass(NBB.hlist or '')
            if not row1.group then list:attr('colspan', 2):addClass('navbox-nogroup') end
            applyCSS(list, 'list', {'list_n', v1})
        end
        if i1 == 1 and section.rimage then
            rimage = tr:tag('td'):newline():wikitext(section.rimage)
            rimage:done():newline()
        end
 
        -- Appearance
        if group then
            group:addClass('navbox-group navbox-base navbox-padding')
            if #keys > 1 then group:attr('rowspan', #keys) end
            applyCSS(group, 'base', 'group', {'group_n', v1})
        end
    end
 
    -- Appearance
    tab:addClass('navbox-table')
 
    for i,v in ipairs(lists) do
        local span = v:getAttr('colspan') or 1
        if deep then span = span + 1 end
        if span == 1 then span = nil end
        v:attr('colspan', span)
    end
 
    if limage then
        limage:addClass('navbox-image')
        applyCSS(limage, 'image', {'limage_n', no})
        if count > 1 then limage:attr('rowspan', count) end
    end
    if rimage then
        rimage:addClass('navbox-image')
        applyCSS(rimage, 'image', {'rimage_n', no})
        if count > 1 then rimage:attr('rowspan', count) end
    end
 
    return tab
end
NBB.formats.horizontal = function(section, no, wrapper)
    local lists, deep, count, keys = {}, false, 0, getKeys(section.rows, true)
    if #keys == 0 then return nil end
 
    -- Structure
    local res, wrap
 
    wrap = tonumber(getValue('perrow_n', no))
    if wrap then
        wrap = math.max(1, wrap)
        -- if wrap > 0 then
        --     wrapper:addClass('navbox-static'):css('--wrap', wrap)
        -- end
    end
 
 
    res = mw.html.create('')
    for i1,v1 in ipairs(keys) do
        local row1 = section.rows[v1]
 
        -- Structure
        local keys = getKeys(row1.rows, true)
        if #keys > 0 then
            for i2,v2 in ipairs(keys) do
                count = count + 1
                local row2 = row1.rows[v2]
 
                -- Structure
                local col, group, list
 
                col = res:tag('div'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd'))
                if row2.group then group = col:tag('div'):wikitext(row2.group) end
 
                if wrap then -- delete in case of css vars
                    col:css('flex-basis', 100/wrap .. '%')
                end
 
                list = col:tag('div'):newline():wikitext(row2.list)
                list:done():newline()
                table.insert(lists, list)
 
                -- Appearance
                col:addClass('navbox-col')
                list:addClass('navbox-list navbox-padding'):addClass(NBB.vlist or '')
                applyCSS(list, 'list', {'list_n', v1})
 
                if group then
                    group:addClass('navbox-group navbox-base navbox-padding')
                    applyCSS(group, 'base', 'group', {'group_n', v1})
                end
            end
        else
            count = count + 1
            -- Structure
            local col, group, list
 
            col = res:tag('div'):addClass('navbox-'..(count % 2 == 0 and 'even' or 'odd'))
            if row1.group then group = col:tag('div'):wikitext(row1.group) end
 
            if wrap then
                local span = math.max(0, math.min(wrap, tonumber(getValue('span_n', v1)) or 1))
                col:css('flex-basis', span/wrap*100 .. '%') -- delete in case of css vars
                -- if span ~= 1 then
                --     col:css('--span', span)
                -- end
            else
                col._span = tonumber(getValue('span_n', v1))
            end
 
            list = col:tag('div'):newline():wikitext(row1.list)
            list:done():newline()
            table.insert(lists, list)
 
            -- Appearance
            col:addClass('navbox-col')
            list:addClass('navbox-list navbox-padding'):addClass(NBB.vlist or '')
            applyCSS(list, 'list', {'list_n', v1})
 
            if group then
                group:addClass('navbox-group navbox-base navbox-padding')
                applyCSS(group, 'base', 'group', {'group_n', v1})
            end
        end
    end
    if not wrap then
        wrap = #lists
        for i,v in ipairs(lists) do
            local col = v.parent
            col:css('flex-basis', math.max(0, math.min(wrap, col._span or 1))/wrap*100 .. '%')
        end
    end
 
    return res
end
 
return NBB
Community content is available under CC-BY-SA unless otherwise noted.

Fandom may earn an affiliate commission on sales made from links on this page.

Stream the best stories.

Fandom may earn an affiliate commission on sales made from links on this page.

Get Disney+