More actions
mNo edit summary Tag: Reverted |
mNo edit summary Tag: Reverted |
||
Line 1: | Line 1: | ||
local canonicalName = { | |||
local | |||
['titlestyle'] = 'title_style', | ['titlestyle'] = 'title_style', | ||
['listclass'] = 'list_class', | ['listclass'] = 'list_class', | ||
Line 71: | Line 18: | ||
} | } | ||
local | |||
[' | |||
[' | |||
local config = { | |||
['default_list_class'] = 'hlist', -- base value of the `list_class` parameter. | |||
['editlink_hover_message_key'] = 'Navbox-edit-hover', -- The system message name for hover text of the edit icon. | |||
} | } | ||
local | local args = {} -- store nomalized args | ||
local tree = {} | |||
local hooks = {} | |||
} | |||
local | local listClass -- default class for lists | ||
local listCss | |||
local groupClass -- default class for groups | |||
local groupCss | |||
local subgroupClass -- default class for subgroups | |||
local subgroupCss | |||
local headerClass -- default class for headers | |||
local headerCss | |||
local | local headerState -- default state for all headers | ||
local | local nvaboxMainClass = 'ranger-navbox' | ||
local classPrefix = 'ranger-' | |||
local trim = mw.text.trim | |||
local | local even -- for zebra stripes | ||
------------------------ | ---Split the `str` on each `div` in it and return the result as a table. Original | ||
---version credit: http://richard.warburton.it. | |||
---@param div string | |||
---@param str string | |||
---@return string[]? strExploded Is `nil` if `div` is an empty string | |||
local function explode(div, str) | |||
if (div=='') then return nil end | |||
local pos,arr = 0,{} | |||
-- for each divider found | |||
for st,sp in function() return string.find(str,div,pos,true) end do | |||
arr[#arr+1] = string.sub(str,pos,st-1) -- Attach chars left of current divider | |||
pos = sp+1 -- Jump past current divider | |||
end | |||
arr[#arr+1] = string.sub(str,pos) -- Attach chars right of last divider | |||
return arr | |||
end | |||
-- | -- Normalize the name string of arguments. | ||
-- space character(" ") will be treat as underscore("_"), | |||
-- and the name string will be converted to lowercase, | |||
-- and support underscore between numbers (n_m_l format), | |||
-- and support such as group1/list1 prefix. | |||
local function normalize(s) | |||
-- camel-case to lowercase underscore-case | |||
s = string.gsub(s, '(%l)(%u)', '%1_%2') | |||
s = string.lower(string.gsub(s, ' ', '_')) | |||
s = string.gsub(s, '(%l)(%d)', '%1_%2') -- group1* to group_1* | |||
s = string.gsub(s, '(%d)(%l)', '%1_%2') -- *1style to *1_style | |||
-- number format x_y_z to x.y.z | |||
s = string.gsub(s, '(%d)_%f[%d]', '%1%.') | |||
-- standardize *_css to *_style | |||
s = string.gsub(s, '_css$', '_style') | |||
-- standardize all aliases to the canonical name | |||
return canonicalName[s] or s | |||
end | end | ||
local function parseArgs(inputArgs) | |||
for k,v in pairs(inputArgs) do | |||
args[normalize(k)] = trim(v) | |||
end | end | ||
end | |||
-- Used to traverses a table following the order of its keys: | |||
-- for key, value in pairsByKeys(array) do | |||
-- print(key, value) | |||
-- end | |||
local function pairsByKeys(t, f) | |||
local a = {} | |||
for n in pairs(t) do table.insert(a, n) end | |||
table.sort(a, f) | |||
local i = 0 -- iterator variable | |||
local iter = function () -- iterator function | |||
i = i + 1 | |||
if a[i] == nil then return nil | |||
else return a[i], t[a[i]] | |||
end | end | ||
end | end | ||
if | return iter | ||
end | |||
local function normalizeStateValue(state) | |||
if state == 'no' or state == 'off' or state == 'plain' then | |||
return nil | |||
end | |||
if state == 'collapsed' then | |||
return 'collapsed' | |||
end | end | ||
return true | |||
end | |||
local function normalizeStripedValue(striped) | |||
if striped == 'odd' or striped == 'swap' then | |||
striped = 'striped-odd' | |||
elseif striped == 'y' or striped == 'yes' or striped == 'on' or striped == 'even' or striped == 'striped' then | |||
if | striped = 'striped-even' | ||
else | |||
striped = nil | |||
end | |||
return striped | |||
end | |||
local function makeCollapsible(node, state) | |||
if state then | |||
node:addClass('mw-collapsible') | |||
if state == 'collapsed' then | |||
node:addClass('mw-collapsed') | |||
end | end | ||
end | end | ||
end | |||
local function runHook(key, ...) | |||
if hooks[key] then | |||
hooks[key](...) | |||
end | |||
end | |||
local function getArg(name) | |||
if args[name] and args[name] ~= '' then | |||
return args[name] | |||
return | |||
else | else | ||
return | return nil | ||
end | |||
end | |||
local function getArgGroup(prefix) | |||
if not prefix then | |||
return tree | |||
end | |||
local node = tree | |||
for _, s in ipairs(explode('.', prefix)) do | |||
if not node[s] then error('invaild index: '..prefix) end | |||
node = node[s]['sub'] | |||
end | end | ||
return node | |||
end | end | ||
local function checkForTreeNode(name, key, value) | |||
local pattern = '^'..name..'_([%.%d]+)$' | |||
local index = string.match(key, pattern) | |||
if not index then return end | |||
if string.match(index, '^%.') or string.match(index, '%.$') or string.match(index, '%.%.') then return end | |||
local arr = explode('.', index) | |||
n = tonumber(table.remove(arr)) | |||
local node = tree | |||
for _, v in ipairs(arr) do | |||
v = tonumber(v) | |||
if not node[v] then | |||
node[v] = {['sub'] = {}} | |||
elseif not node[v]['sub'] then | |||
node[v]['sub'] = {} | |||
local | |||
for | |||
v = | |||
if v | |||
end | end | ||
node = node[v]['sub'] | |||
end | end | ||
if not node[n] then node[n] = {} end | |||
if name == 'list' and string.sub(value, 1, 13) == '!!C$H$I$L$D!!' then | |||
-- it is from {{navbox|child| ... }} | |||
node[n]['sub'] = mw.text.jsonDecode(string.sub(value, 14)) | |||
else | |||
node[n][name] = value | |||
end | end | ||
return | return true | ||
end | end | ||
local function buildTree() | |||
for k, v in pairs(args) do | |||
local _ = checkForTreeNode('list', k, v) or checkForTreeNode('group', k, v) or checkForTreeNode('header', k, v) | |||
end | |||
return tree | |||
end | |||
function | local function getMergedStr(...) | ||
local s = '' | |||
for i=1, select('#', ...) do | |||
local v = trim(select(i, ...) or '') | |||
local str = string.match(v, '^%-%-+(.*)$') | |||
if str then | |||
s = trim(str..' '..s) | |||
-- | break | ||
if | |||
else | else | ||
s = trim(v..' '..s) | |||
end | end | ||
end | end | ||
if s == '' then s = nil end | |||
return s | |||
return | |||
end | end | ||
local function getCssArg(prefix) | |||
local css = getArg(prefix..'_style') | |||
if css and (string.sub(css, -1) ~= ';') then | |||
css = css..';' | |||
end | |||
return css | |||
end | |||
-- Applies class/css to the element | |||
local function applyStyle(node, prefix, baseClass, baseCss) | |||
return node:addClass(getMergedStr(getArg(prefix..'_class'), baseClass)):cssText(getMergedStr(getCssArg(prefix), baseCss)) | |||
end | end | ||
function | local function processItem(item) | ||
if item:sub(1, 2) == '{|' then | |||
if | |||
-- Applying nowrap to lines in a table does not make sense. | -- Applying nowrap to lines in a table does not make sense. | ||
-- Add newlines to compensate for trim of x in |parm=x in a template. | -- Add newlines to compensate for trim of x in |parm=x in a template. | ||
return '\n' .. | return '\n' .. item ..'\n' | ||
end | end | ||
if item:match('^[*:;#]') then | |||
return '\n' .. item ..'\n' | |||
end | |||
return item | |||
return | |||
end | end | ||
local function renderMetaLinks() | |||
function | local name = getArg('name') or mw.getCurrentFrame():getParent():getTitle() | ||
local | |||
local title = mw.title.new(trim(name), 'Template') | |||
if not title then | |||
error('Invalid title ' .. name) | |||
end | end | ||
local msg = mw.message.new(config['editlink_hover_message_key']) | |||
local hoverText = msg:exists() and msg:plain() or 'View or edit this template' | |||
return mw.html.create('span'):addClass(classPrefix..'meta') | |||
:tag('span'):addClass('nv nv-view') | |||
:wikitext('[['..title.fullText..'|') | |||
:tag('span'):wikitext(hoverText):attr('title', hoverText):done() | |||
:wikitext(']]') | |||
:done() | |||
end | |||
local function renderTitleBar(title, collapsible, metaLinks) | |||
local titlebar = applyStyle(mw.html.create('div'):addClass(classPrefix..'title'), 'title', config['default_title_class']) | |||
if metaLinks then | |||
titlebar:node(renderMetaLinks()) | |||
end | end | ||
if title then | |||
titlebar:tag('div') | |||
:attr('id', mw.uri.anchorEncode(title) or '') -- id for aria-labelledby attribute | |||
:addClass(classPrefix..'title-text') | |||
:wikitext(processItem(title)) | |||
end | end | ||
return titlebar | |||
end | |||
local | |||
local | local function renderAboveBox(above, id) | ||
local | local node = mw.html.create('div') | ||
:addClass(classPrefix..'above mw-collapsible-content') | |||
local | -- id for aria-labelledby attribute, if no title | ||
:attr('id', id and mw.uri.anchorEncode(above) or nil) | |||
:wikitext(processItem(above)) | |||
return applyStyle(node, 'above', config['default_above_class']) | |||
end | |||
local function renderBelowBox(below) | |||
local node = mw.html.create('div') | |||
:addClass(classPrefix..'below mw-collapsible-content') | |||
:wikitext(processItem(below)) | |||
return applyStyle(node, 'below', config['default_below_class']) | |||
- | end | ||
local function renderSectionHeader(content, index) | |||
local node = mw.html.create('div'):addClass(classPrefix..'header') | |||
:tag('div'):addClass(classPrefix..'header-text'):wikitext(processItem(content)) | |||
:done() | |||
return applyStyle(node, 'header_'..index, headerClass, getCssArg('header')) | |||
end | |||
local function renderList(content, index, level) | |||
even = not even -- flip even/odd status | |||
local node = mw.html.create('div'):addClass(classPrefix..'wrap'):addClass(even and classPrefix..'even' or classPrefix..'odd') | |||
:tag('div'):addClass(classPrefix..'list'):wikitext(processItem(content)) | |||
:done() | |||
return applyStyle(node, 'list_'..index, | |||
getMergedStr(getArg('list_level_'..level..'_class'), listClass), | |||
getMergedStr(getCssArg('list_level_'..level), config['default_list_level_'..level..'_class'], listCss) | |||
) | |||
end | |||
local renderRow, renderSublist | |||
function renderRow(box, v, k, level) | |||
if v['group'] or v['list'] or v['sub'] then | |||
local row = box:tag('div'):addClass(classPrefix..'row') | |||
if v['group'] or (v['sub'] and level > 0 and not v['group'] and not v['list']) then | |||
local groupCell = row:tag('div') | |||
if level == 0 then | |||
groupCell:addClass(classPrefix..'group') | |||
applyStyle(groupCell, 'group_'..k, groupClass, groupCss) | |||
else | |||
groupCell:addClass(classPrefix..'subgroup level-'..level) | |||
:addClass(getMergedStr( | |||
getArg('group_'..k..'_class'), | |||
getArg('subgroup_'..k..'_class'), | |||
getArg('subgroup_level_'..level..'_class'), | |||
config['default_subgroup_level_'..level..'_class'], | |||
subgroupClass | |||
)) | |||
:cssText(getMergedStr( | |||
getCssArg('group_'..k), | |||
getCssArg('subgroup_'..k), | |||
getCssArg('subgroup_level_'..level), | |||
subgroupCss | |||
)) | |||
end | |||
groupCell:tag('div'):addClass(classPrefix..'wrap'):wikitext(processItem(v['group'] or '')) | |||
if not v['group'] then | |||
groupCell:addClass('empty') | |||
row:addClass('empty-group-list') | |||
end | end | ||
else | |||
row:addClass('empty-group') | |||
end | end | ||
local listCell = row:tag('div'):addClass(classPrefix..'listbox') | |||
if v | if not v['list'] and not v['sub'] then | ||
listCell:addClass('empty') | |||
row:addClass('empty-list') | |||
end | end | ||
if v | if v['list'] or (v['group'] and not v['sub']) then | ||
listCell:node(renderList(v['list'] or '', k, level)) | |||
end | end | ||
if | if v['sub'] then | ||
listCell:node(renderSublist(v['sub'], k, level+1)) | |||
end | end | ||
return box | |||
end | end | ||
end | end | ||
function renderSublist(l, prefix, level) | |||
function | local count = 0 | ||
local | local box = mw.html.create('div'):addClass(classPrefix..'sublist level-'..level) | ||
local | for k,v in pairsByKeys(l) do | ||
count = count + tonumber(renderRow(box, v, prefix..'.'..k, level) and 1 or 0) | |||
end | end | ||
if count > 0 then | |||
return box:css('--count', count) | |||
end | end | ||
end | end | ||
function | local function build(inputArgs) | ||
if mw.title.new('Module:Navbox/Hooks').exists then | |||
hooks = require('Module:Navbox/Hooks') | |||
end | end | ||
runHook('onParseArgs', inputArgs) | |||
parseArgs(inputArgs) | |||
buildTree() | |||
listClass = getMergedStr(getArg('list_class'), config['default_list_class']) | |||
listCss = getCssArg('list') | |||
groupClass = getMergedStr(getArg('group_class'), config['default_group_class']) | |||
groupCss = getCssArg('group') | |||
subgroupClass = getMergedStr(getArg('subgroup_class'), config['default_subgroup_class']) | |||
subgroupCss = getCssArg('subgroup') | |||
headerClass = getMergedStr(getArg('header_class'), config['default_header_class']) | |||
headerCss = getCssArg('header') | |||
headerState = getArg('header_state') | |||
local | local res = mw.html.create() | ||
local collapsible = normalizeStateValue(getArg('state')) | |||
local metaLinks = normalizeStateValue(getArg('meta')) | |||
local title = getArg('title') | |||
local above = getArg('above') | |||
local below = getArg('below') | |||
local striped = normalizeStripedValue(getArg('striped')) | |||
-- build navbox container | -- build navbox container | ||
local | |||
:attr('role', 'navigation'): | local navboxClass = getMergedStr(getArg('navbox_class'), config['default_navbox_class']) | ||
:addClass( | local nav = res:tag('div') | ||
:addClass( | :attr('role', 'navigation') | ||
: | :addClass(nvaboxMainClass) | ||
: | :addClass(navboxClass) | ||
: | :addClass(striped) | ||
:cssText(getCssArg('navbox')) | |||
--title bar | makeCollapsible(nav, collapsible) | ||
-- aria-labelledby title, otherwise above | |||
if title or above then | |||
nav:attr('aria-labelledby', mw.uri.anchorEncode(title or above)) | |||
else | |||
nav:attr('aria-label', 'Navbox') | |||
end | |||
-- title bar | |||
if title or collapsible or metaLinks then | if title or collapsible or metaLinks then | ||
nav:node(renderTitleBar(title, collapsible, metaLinks)) | nav:node(renderTitleBar(title, collapsible, metaLinks)) | ||
end | end | ||
--above | -- above | ||
if | if above then | ||
nav:node(renderAboveBox(above, not title)) | |||
end | end | ||
-- sections | -- sections | ||
for | local section, box | ||
--section | local sectionClass = getMergedStr(getArg('section_class'), config['default_section_class']) | ||
for k,v in pairsByKeys(tree) do | |||
--start a new section | |||
if v['header'] or not section then | |||
section = nav:tag('div'):addClass(classPrefix..'section mw-collapsible-content') | |||
applyStyle(section, 'section_'..k, sectionClass) | |||
even = true -- reset even/odd status | |||
if v['header'] then | |||
local state = 'plain' | |||
if getMergedStr(v['header']) then | |||
section:node(renderSectionHeader(v['header'], k)) | |||
state = getArg('state_'..k) or headerState | |||
end | |||
makeCollapsible(section, normalizeStateValue(state)) | |||
end | |||
box = section:tag('div'):addClass(classPrefix..'section-body mw-collapsible-content') | |||
end | end | ||
renderRow(box, v, k, 0) | |||
end | end | ||
-- | |||
if | -- Add a blank section for completely empty navbox to ensure it behaves correctly when collapsed. | ||
if not section and not above and not below then | |||
nav:tag('div'):addClass(classPrefix..'section mw-collapsible-content') | |||
end | end | ||
--below | -- below | ||
if | if below then | ||
nav:node(renderBelowBox(below)) | |||
end | end | ||
return | return tostring(res) | ||
end | end | ||
--------------------------------------------------------------------- | |||
return { | |||
navbox = function(frame) | |||
local inputArgs = {} | |||
for k, v in pairs(frame.args) do | |||
if type(k) == 'string' then | |||
v = trim(tostring(v)) | |||
if v ~= '' then | |||
inputArgs[k] = v | |||
end | end | ||
end | end | ||
end | |||
for k, v in pairs(frame:getParent().args) do | |||
if type(k) == 'string' then | |||
v = trim(v) | |||
if v ~= '' then | |||
inputArgs[k] = v | |||
if | |||
end | end | ||
end | end | ||
end | end | ||
if trim(frame.args[1] or frame:getParent().args[1] or '') == 'child' then | |||
parseArgs(inputArgs) | |||
return '!!C$H$I$L$D!!'..mw.text.jsonEncode(buildTree()) | |||
else | else | ||
return build(inputArgs) | |||
end | end | ||
end | end, | ||
build = build, -- for other modules. e.g: return require('module:navbox').build(args) | |||
} | |||
---- | -- version: r59 2024.10.28 -- | ||
Revision as of 03:25, 1 January 2025
Documentation for this module may be created at Module:Navbox/doc
local canonicalName = {
['titlestyle'] = 'title_style',
['listclass'] = 'list_class',
['groupstyle'] = 'group_style',
['collapsible'] = 'state',
['editlink'] = 'meta',
['editlinks'] = 'meta',
['editicon'] = 'meta',
['edit_link'] = 'meta',
['edit_links'] = 'meta',
['edit_icon'] = 'meta',
['navbar'] = 'meta',
['evenodd'] = 'striped',
['class'] = 'navbox_class',
['css'] = 'navbox_style',
['style'] = 'navbox_style',
}
local config = {
['default_list_class'] = 'hlist', -- base value of the `list_class` parameter.
['editlink_hover_message_key'] = 'Navbox-edit-hover', -- The system message name for hover text of the edit icon.
}
local args = {} -- store nomalized args
local tree = {}
local hooks = {}
local listClass -- default class for lists
local listCss
local groupClass -- default class for groups
local groupCss
local subgroupClass -- default class for subgroups
local subgroupCss
local headerClass -- default class for headers
local headerCss
local headerState -- default state for all headers
local nvaboxMainClass = 'ranger-navbox'
local classPrefix = 'ranger-'
local trim = mw.text.trim
local even -- for zebra stripes
---Split the `str` on each `div` in it and return the result as a table. Original
---version credit: http://richard.warburton.it.
---@param div string
---@param str string
---@return string[]? strExploded Is `nil` if `div` is an empty string
local function explode(div, str)
if (div=='') then return nil end
local pos,arr = 0,{}
-- for each divider found
for st,sp in function() return string.find(str,div,pos,true) end do
arr[#arr+1] = string.sub(str,pos,st-1) -- Attach chars left of current divider
pos = sp+1 -- Jump past current divider
end
arr[#arr+1] = string.sub(str,pos) -- Attach chars right of last divider
return arr
end
-- Normalize the name string of arguments.
-- space character(" ") will be treat as underscore("_"),
-- and the name string will be converted to lowercase,
-- and support underscore between numbers (n_m_l format),
-- and support such as group1/list1 prefix.
local function normalize(s)
-- camel-case to lowercase underscore-case
s = string.gsub(s, '(%l)(%u)', '%1_%2')
s = string.lower(string.gsub(s, ' ', '_'))
s = string.gsub(s, '(%l)(%d)', '%1_%2') -- group1* to group_1*
s = string.gsub(s, '(%d)(%l)', '%1_%2') -- *1style to *1_style
-- number format x_y_z to x.y.z
s = string.gsub(s, '(%d)_%f[%d]', '%1%.')
-- standardize *_css to *_style
s = string.gsub(s, '_css$', '_style')
-- standardize all aliases to the canonical name
return canonicalName[s] or s
end
local function parseArgs(inputArgs)
for k,v in pairs(inputArgs) do
args[normalize(k)] = trim(v)
end
end
-- Used to traverses a table following the order of its keys:
-- for key, value in pairsByKeys(array) do
-- print(key, value)
-- end
local function pairsByKeys(t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then return nil
else return a[i], t[a[i]]
end
end
return iter
end
local function normalizeStateValue(state)
if state == 'no' or state == 'off' or state == 'plain' then
return nil
end
if state == 'collapsed' then
return 'collapsed'
end
return true
end
local function normalizeStripedValue(striped)
if striped == 'odd' or striped == 'swap' then
striped = 'striped-odd'
elseif striped == 'y' or striped == 'yes' or striped == 'on' or striped == 'even' or striped == 'striped' then
striped = 'striped-even'
else
striped = nil
end
return striped
end
local function makeCollapsible(node, state)
if state then
node:addClass('mw-collapsible')
if state == 'collapsed' then
node:addClass('mw-collapsed')
end
end
end
local function runHook(key, ...)
if hooks[key] then
hooks[key](...)
end
end
local function getArg(name)
if args[name] and args[name] ~= '' then
return args[name]
else
return nil
end
end
local function getArgGroup(prefix)
if not prefix then
return tree
end
local node = tree
for _, s in ipairs(explode('.', prefix)) do
if not node[s] then error('invaild index: '..prefix) end
node = node[s]['sub']
end
return node
end
local function checkForTreeNode(name, key, value)
local pattern = '^'..name..'_([%.%d]+)$'
local index = string.match(key, pattern)
if not index then return end
if string.match(index, '^%.') or string.match(index, '%.$') or string.match(index, '%.%.') then return end
local arr = explode('.', index)
n = tonumber(table.remove(arr))
local node = tree
for _, v in ipairs(arr) do
v = tonumber(v)
if not node[v] then
node[v] = {['sub'] = {}}
elseif not node[v]['sub'] then
node[v]['sub'] = {}
end
node = node[v]['sub']
end
if not node[n] then node[n] = {} end
if name == 'list' and string.sub(value, 1, 13) == '!!C$H$I$L$D!!' then
-- it is from {{navbox|child| ... }}
node[n]['sub'] = mw.text.jsonDecode(string.sub(value, 14))
else
node[n][name] = value
end
return true
end
local function buildTree()
for k, v in pairs(args) do
local _ = checkForTreeNode('list', k, v) or checkForTreeNode('group', k, v) or checkForTreeNode('header', k, v)
end
return tree
end
local function getMergedStr(...)
local s = ''
for i=1, select('#', ...) do
local v = trim(select(i, ...) or '')
local str = string.match(v, '^%-%-+(.*)$')
if str then
s = trim(str..' '..s)
break
else
s = trim(v..' '..s)
end
end
if s == '' then s = nil end
return s
end
local function getCssArg(prefix)
local css = getArg(prefix..'_style')
if css and (string.sub(css, -1) ~= ';') then
css = css..';'
end
return css
end
-- Applies class/css to the element
local function applyStyle(node, prefix, baseClass, baseCss)
return node:addClass(getMergedStr(getArg(prefix..'_class'), baseClass)):cssText(getMergedStr(getCssArg(prefix), baseCss))
end
local function processItem(item)
if item:sub(1, 2) == '{|' then
-- Applying nowrap to lines in a table does not make sense.
-- Add newlines to compensate for trim of x in |parm=x in a template.
return '\n' .. item ..'\n'
end
if item:match('^[*:;#]') then
return '\n' .. item ..'\n'
end
return item
end
local function renderMetaLinks()
local name = getArg('name') or mw.getCurrentFrame():getParent():getTitle()
local title = mw.title.new(trim(name), 'Template')
if not title then
error('Invalid title ' .. name)
end
local msg = mw.message.new(config['editlink_hover_message_key'])
local hoverText = msg:exists() and msg:plain() or 'View or edit this template'
return mw.html.create('span'):addClass(classPrefix..'meta')
:tag('span'):addClass('nv nv-view')
:wikitext('[['..title.fullText..'|')
:tag('span'):wikitext(hoverText):attr('title', hoverText):done()
:wikitext(']]')
:done()
end
local function renderTitleBar(title, collapsible, metaLinks)
local titlebar = applyStyle(mw.html.create('div'):addClass(classPrefix..'title'), 'title', config['default_title_class'])
if metaLinks then
titlebar:node(renderMetaLinks())
end
if title then
titlebar:tag('div')
:attr('id', mw.uri.anchorEncode(title) or '') -- id for aria-labelledby attribute
:addClass(classPrefix..'title-text')
:wikitext(processItem(title))
end
return titlebar
end
local function renderAboveBox(above, id)
local node = mw.html.create('div')
:addClass(classPrefix..'above mw-collapsible-content')
-- id for aria-labelledby attribute, if no title
:attr('id', id and mw.uri.anchorEncode(above) or nil)
:wikitext(processItem(above))
return applyStyle(node, 'above', config['default_above_class'])
end
local function renderBelowBox(below)
local node = mw.html.create('div')
:addClass(classPrefix..'below mw-collapsible-content')
:wikitext(processItem(below))
return applyStyle(node, 'below', config['default_below_class'])
end
local function renderSectionHeader(content, index)
local node = mw.html.create('div'):addClass(classPrefix..'header')
:tag('div'):addClass(classPrefix..'header-text'):wikitext(processItem(content))
:done()
return applyStyle(node, 'header_'..index, headerClass, getCssArg('header'))
end
local function renderList(content, index, level)
even = not even -- flip even/odd status
local node = mw.html.create('div'):addClass(classPrefix..'wrap'):addClass(even and classPrefix..'even' or classPrefix..'odd')
:tag('div'):addClass(classPrefix..'list'):wikitext(processItem(content))
:done()
return applyStyle(node, 'list_'..index,
getMergedStr(getArg('list_level_'..level..'_class'), listClass),
getMergedStr(getCssArg('list_level_'..level), config['default_list_level_'..level..'_class'], listCss)
)
end
local renderRow, renderSublist
function renderRow(box, v, k, level)
if v['group'] or v['list'] or v['sub'] then
local row = box:tag('div'):addClass(classPrefix..'row')
if v['group'] or (v['sub'] and level > 0 and not v['group'] and not v['list']) then
local groupCell = row:tag('div')
if level == 0 then
groupCell:addClass(classPrefix..'group')
applyStyle(groupCell, 'group_'..k, groupClass, groupCss)
else
groupCell:addClass(classPrefix..'subgroup level-'..level)
:addClass(getMergedStr(
getArg('group_'..k..'_class'),
getArg('subgroup_'..k..'_class'),
getArg('subgroup_level_'..level..'_class'),
config['default_subgroup_level_'..level..'_class'],
subgroupClass
))
:cssText(getMergedStr(
getCssArg('group_'..k),
getCssArg('subgroup_'..k),
getCssArg('subgroup_level_'..level),
subgroupCss
))
end
groupCell:tag('div'):addClass(classPrefix..'wrap'):wikitext(processItem(v['group'] or ''))
if not v['group'] then
groupCell:addClass('empty')
row:addClass('empty-group-list')
end
else
row:addClass('empty-group')
end
local listCell = row:tag('div'):addClass(classPrefix..'listbox')
if not v['list'] and not v['sub'] then
listCell:addClass('empty')
row:addClass('empty-list')
end
if v['list'] or (v['group'] and not v['sub']) then
listCell:node(renderList(v['list'] or '', k, level))
end
if v['sub'] then
listCell:node(renderSublist(v['sub'], k, level+1))
end
return box
end
end
function renderSublist(l, prefix, level)
local count = 0
local box = mw.html.create('div'):addClass(classPrefix..'sublist level-'..level)
for k,v in pairsByKeys(l) do
count = count + tonumber(renderRow(box, v, prefix..'.'..k, level) and 1 or 0)
end
if count > 0 then
return box:css('--count', count)
end
end
local function build(inputArgs)
if mw.title.new('Module:Navbox/Hooks').exists then
hooks = require('Module:Navbox/Hooks')
end
runHook('onParseArgs', inputArgs)
parseArgs(inputArgs)
buildTree()
listClass = getMergedStr(getArg('list_class'), config['default_list_class'])
listCss = getCssArg('list')
groupClass = getMergedStr(getArg('group_class'), config['default_group_class'])
groupCss = getCssArg('group')
subgroupClass = getMergedStr(getArg('subgroup_class'), config['default_subgroup_class'])
subgroupCss = getCssArg('subgroup')
headerClass = getMergedStr(getArg('header_class'), config['default_header_class'])
headerCss = getCssArg('header')
headerState = getArg('header_state')
local res = mw.html.create()
local collapsible = normalizeStateValue(getArg('state'))
local metaLinks = normalizeStateValue(getArg('meta'))
local title = getArg('title')
local above = getArg('above')
local below = getArg('below')
local striped = normalizeStripedValue(getArg('striped'))
-- build navbox container
local navboxClass = getMergedStr(getArg('navbox_class'), config['default_navbox_class'])
local nav = res:tag('div')
:attr('role', 'navigation')
:addClass(nvaboxMainClass)
:addClass(navboxClass)
:addClass(striped)
:cssText(getCssArg('navbox'))
makeCollapsible(nav, collapsible)
-- aria-labelledby title, otherwise above
if title or above then
nav:attr('aria-labelledby', mw.uri.anchorEncode(title or above))
else
nav:attr('aria-label', 'Navbox')
end
-- title bar
if title or collapsible or metaLinks then
nav:node(renderTitleBar(title, collapsible, metaLinks))
end
-- above
if above then
nav:node(renderAboveBox(above, not title))
end
-- sections
local section, box
local sectionClass = getMergedStr(getArg('section_class'), config['default_section_class'])
for k,v in pairsByKeys(tree) do
--start a new section
if v['header'] or not section then
section = nav:tag('div'):addClass(classPrefix..'section mw-collapsible-content')
applyStyle(section, 'section_'..k, sectionClass)
even = true -- reset even/odd status
if v['header'] then
local state = 'plain'
if getMergedStr(v['header']) then
section:node(renderSectionHeader(v['header'], k))
state = getArg('state_'..k) or headerState
end
makeCollapsible(section, normalizeStateValue(state))
end
box = section:tag('div'):addClass(classPrefix..'section-body mw-collapsible-content')
end
renderRow(box, v, k, 0)
end
-- Add a blank section for completely empty navbox to ensure it behaves correctly when collapsed.
if not section and not above and not below then
nav:tag('div'):addClass(classPrefix..'section mw-collapsible-content')
end
-- below
if below then
nav:node(renderBelowBox(below))
end
return tostring(res)
end
---------------------------------------------------------------------
return {
navbox = function(frame)
local inputArgs = {}
for k, v in pairs(frame.args) do
if type(k) == 'string' then
v = trim(tostring(v))
if v ~= '' then
inputArgs[k] = v
end
end
end
for k, v in pairs(frame:getParent().args) do
if type(k) == 'string' then
v = trim(v)
if v ~= '' then
inputArgs[k] = v
end
end
end
if trim(frame.args[1] or frame:getParent().args[1] or '') == 'child' then
parseArgs(inputArgs)
return '!!C$H$I$L$D!!'..mw.text.jsonEncode(buildTree())
else
return build(inputArgs)
end
end,
build = build, -- for other modules. e.g: return require('module:navbox').build(args)
}
-- version: r59 2024.10.28 --