Module:Navbox: Difference between revisions

From Polcompball Wiki
Jump to navigationJump to search
Content added Content deleted
(merge nomobile version of styles live)
No edit summary
Line 1: Line 1:
--------------------------------------------------------------------
--<pre> Navbox Module
--
--
-- * Fully CSS styled (inline styles possible but not default)
-- This module implements {{Navbox}}
-- * Supports unlimited rows
--
--
-- By User:Tjcool007 from layton.fandom.com
--------------------------------------------------------------------


local p = {}
local p = {}


local args = {} -- Arguments passed to template
local navbar = require('Module:Navbar')._navbar
local getArgs -- lazily initialized
local navbox -- Actual navbox


local args
--local working = {}
local rownums, skiprows = {}, {}
local border
local hasrows, alt, hasData, isChild = false, false, false, false
local listnums
local activeSection, sections, cimage, cimageleft
local ODD_EVEN_MARKER = '\127_ODDEVEN_\127'
local colspan, rowspan
local RESTART_MARKER = '\127_ODDEVEN0_\127'
local REGEX_MARKER = '\127_ODDEVEN(%d?)_\127'


local showText, hideText = 'Show', 'Hide'
local function striped(wikitext)

-- Return wikitext with markers replaced for odd/even striping.
local langCode = mw.getContentLanguage():getCode()
-- Child (subgroup) navboxes are flagged with a category that is removed
local localization = {} --localized strings table
-- by parent navboxes. The result is that the category shows all pages
localization['bn'] = {show = 'দেখান', hide = 'লুকান'}
-- where a child navbox is not contained in a parent navbox.
localization['de'] = {show = 'Ausklappen', hide = 'Einklappen'}
local orphanCat = '[[Category:Navbox orphans]]'
localization['en'] = {show = 'Show', hide = 'Hide'}
if border == 'subgroup' and args.orphan ~= 'yes' then
localization['es'] = {show = 'Mostrar', hide = 'Ocultar'}
-- No change; striping occurs in outermost navbox.
localization['hi'] = {show = 'दिखाएँ', hide = 'छिपाएँ'}
return wikitext .. orphanCat
localization['ja'] = {show = '表示', hide = '隠す'}
localization['ru'] = {show = 'показать', hide = 'скрыть'}
localization['zh'] = {show = '显示', hide = '隐藏'}
if localization[langCode] then
showText = localization[langCode]['show']
hideText = localization[langCode]['hide']
end

------------------------------------------------
-- Title
------------------------------------------------

--- Processes the VDE links in the title
--
-- @param titlecell The table cell of the title
local function processVde( titlecell )
if not args.template then return end

titlecell:wikitext('<span class="navbox-vde">'
.. mw.getCurrentFrame():expandTemplate({
title = 'vdelinks',
args = { args.template, ['type'] = 'navbox' }
}) .. '</span>')
end

--- Processes the main title row
local function processTitle()
local titlerow = mw.html.create('tr'):addClass('navbox-title')
local titlecell = mw.html.create('th'):attr('colspan',colspan):attr('scope','col')

if not pcall( processVde, titlecell ) then
titlecell:wikitext( '<b class="navbox-vde error" title="Missing Template:Vdelinks">!!!</b>' )
end
end

local first, second = 'odd', 'even'
titlecell:wikitext( args.title or '{{{title}}}' )
if args.evenodd then

if args.evenodd == 'swap' then
-- Padding
first, second = second, first
local hasTemplate = args.template ~= nil
local hasState = not args.state or args.state ~= 'plain'

if hasTemplate ~= hasState then
if hasTemplate then
titlecell:addClass('navbox-title-padright')
else
else
titlecell:addClass('navbox-title-padleft')
first = args.evenodd
second = first
end
end
end
end

local changer
if args.titleclass then titlerow:addClass( args.titleclass ) end
if first == second then
if args.titlestyle then titlecell:cssText( args.titlestyle ) end
changer = first

else
titlerow:node(titlecell)
local index = 0
navbox:node(titlerow)
changer = function (code)
if code == '0' then
-- Current occurrence is for a group before a nested table.
-- Set it to first as a valid although pointless class.
-- The next occurrence will be the first row after a title
-- in a subgroup and will also be first.
index = 0
return first
end
index = index + 1
return index % 2 == 1 and first or second
end
end
local regex = orphanCat:gsub('([%[%]])', '%%%1')
return (wikitext:gsub(regex, ''):gsub(REGEX_MARKER, changer)) -- () omits gsub count
end
end


local function processItem(item, nowrapitems)
local function _addGutter( parent, incRowspan )
parent:tag('tr'):addClass('navbox-gutter'):tag('td'):attr('colspan',2)
if item:sub(1, 2) == '{|' then

-- Applying nowrap to lines in a table does not make sense.
if incRowspan then
-- Add newlines to compensate for trim of x in |parm=x in a template.
rowspan = rowspan + 1
return '\n' .. item ..'\n'
end
end
if nowrapitems == 'yes' then
local lines = {}
for line in (item .. '\n'):gmatch('([^\n]*)\n') do
local prefix, content = line:match('^([*:;#]+)%s*(.*)')
if prefix and not content:match('^<span class="nowrap">') then
line = prefix .. '<span class="nowrap">' .. content .. '</span>'
end
table.insert(lines, line)
end
item = table.concat(lines, '\n')
end
if item:match('^[*:;#]') then
return '\n' .. item ..'\n'
end
return item
end
end


------------------------------------------------
-- Separate function so that we can evaluate properly whether hlist should
-- Above/Below
-- be added by the module
------------------------------------------------
local function has_navbar()
return args.navbar ~= 'off' and args.navbar ~= 'plain' and
(args.name or mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') ~= 'Template:Navbox')
end


--- Processes the above and below rows
local function renderNavBar(titleCell)
--
-- @param rowtype Either 'above' or 'below'
local function processAboveBelow( rowtype )
if not args[rowtype] then return end


local abrow = mw.html.create('tr'):addClass('navbox-'..rowtype)
if has_navbar() then
local abcell = mw.html.create('td'):attr('colspan',colspan):wikitext( args[rowtype] )
titleCell:wikitext(navbar{
args.name,
-- we depend on this being mini = 1 when the navbox module decides
-- to add hlist templatestyles. we also depend on navbar outputting
-- a copy of the hlist templatestyles.
mini = 1,
fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;box-shadow:none; padding:0;'
})
end


if args[rowtype .. 'class'] then abrow:addClass( args[rowtype .. 'class'] ) end
if args[rowtype .. 'style'] then abcell:cssText( args[rowtype .. 'style'] ) end

abrow:node( abcell )
_addGutter( navbox )
navbox:node( abrow )
end
end


------------------------------------------------
--
-- Title row
-- Main Rows
------------------------------------------------
--
local function renderTitleRow(tbl)
if not args.title then return end


--- Processes the images
local titleRow = tbl:tag('tr')
local function _processImage(row, imgtype)
if not args[imgtype] then return end


local iclass = imgtype == 'image' and 'navbox-image-right' or 'navbox-image-left'
if args.titlegroup then

titleRow
local imagecell = mw.html.create('td'):addClass('navbox-image'):addClass(iclass)
:tag('th')

:attr('scope', 'row')
local image = args[imgtype]
:addClass('navbox-group')
if image:sub(1,1) ~= '[' then
:addClass(args.titlegroupclass)
local width = args[imgtype .. 'width'] or '100px'
:cssText(args.basestyle)
imagecell:css('width',width):wikitext('['..'[' .. image .. '|' .. width .. '|link=' .. (args[imgtype .. 'link'] or '') .. ']]')
:cssText(args.groupstyle)
else
:cssText(args.titlegroupstyle)
:wikitext(args.titlegroup)
imagecell:css('width','0%'):wikitext(image)
end
end


if args[imgtype .. 'class'] then imagecell:addClass( args[imgtype .. 'class'] ) end
local titleCell = titleRow:tag('th'):attr('scope', 'col')
if args[imgtype .. 'style'] then imagecell:cssText( args[imgtype .. 'style'] ) end


row:node( imagecell )
if args.titlegroup then
if imgtype == 'image' then
titleCell
cimage = imagecell
:addClass('navbox-title1')
else
cimageleft = imagecell
end
end
end


--- Closes the currently active section (if any)
local titleColspan = 2
local function _closeCurrentSection()
if args.imageleft then titleColspan = titleColspan + 1 end
if args.image then titleColspan = titleColspan + 1 end
if not activeSection then return end
if args.titlegroup then titleColspan = titleColspan - 1 end


local row = mw.html.create('tr'):addClass('navbox-section-row')
titleCell
local cell = mw.html.create('td'):attr('colspan',2)
:cssText(args.basestyle)

:cssText(args.titlestyle)
if not hasrows then
:addClass('navbox-title')
_processImage(row,'imageleft')
:attr('colspan', titleColspan)
end

cell:node(sections[activeSection])
row:node(cell)

local firstRow = false
if not hasrows then
firstRow = true
hasrows = true
_processImage(row,'image')
end


_addGutter(navbox,not firstRow)
renderNavBar(titleCell)
navbox:node(row)
rowspan = rowspan + 1


activeSection = false
titleCell
hasData = false
:tag('div')
-- id for aria-labelledby attribute
:attr('id', mw.uri.anchorEncode(args.title))
:addClass(args.titleclass)
:css('font-size', '114%')
:css('margin', '0 4em')
:wikitext(processItem(args.title))
end
end


--- Handles alternating rows
--
--
-- @return Alternatingly returns true or false. Always returns false if alternating rows
-- Above/Below rows
-- are disabled with "alternaterows = no"
--
local function _alternateRow()

if args.alternaterows == 'no' then return false end
local function getAboveBelowColspan()
if alt then
local ret = 2
alt = false
if args.imageleft then ret = ret + 1 end
return true
if args.image then ret = ret + 1 end
else
return ret
alt = true
return false
end
end
end


--- Process a single Header "row"
local function renderAboveRow(tbl)
--
if not args.above then return end
-- @param num Number of the row to be processed
local function processHeader(num)
if not args['header'..num] then return end


_closeCurrentSection()
tbl:tag('tr')
:tag('td')
:addClass('navbox-abovebelow')
:addClass(args.aboveclass)
:cssText(args.basestyle)
:cssText(args.abovestyle)
:attr('colspan', getAboveBelowColspan())
:tag('div')
-- id for aria-labelledby attribute, if no title
:attr('id', args.title and nil or mw.uri.anchorEncode(args.above))
:wikitext(processItem(args.above, args.nowrapitems))
end


local subtable = mw.html.create('table'):addClass('navbox-section')
local function renderBelowRow(tbl)
local headerrow = mw.html.create('tr')
if not args.below then return end
local header = mw.html.create('th'):addClass('navbox-header'):attr('colspan',2):attr('scope','col'):wikitext( args['header'..num] )


local collapseme = args['state'..num] or false
tbl:tag('tr')
local state = false
:tag('td')
:addClass('navbox-abovebelow')
:addClass(args.belowclass)
:cssText(args.basestyle)
:cssText(args.belowstyle)
:attr('colspan', getAboveBelowColspan())
:tag('div')
:wikitext(processItem(args.below, args.nowrapitems))
end


if collapseme then
--
-- List rows
-- Look at this one
if collapseme ~= 'plain' then
--
state = collapseme == 'expanded' and 'expanded' or 'collapsed'
local function renderListRow(tbl, index, listnum)
end
local row = tbl:tag('tr')
else
-- Look at default
local collapseall = args.defaultstate or false
if collapseall then
state = collapseall == 'expanded' and 'expanded' or 'collapsed'
end
end


if index == 1 and args.imageleft then
if state then
subtable:addClass('mw-collapsible'):attr('data-expandtext',args['expandtext'..num] or args['defaultexpandtext'] or showText):attr('data-collapsetext',args['collapsetext'..num] or args['defaultcollapsetext'] or hideText)
row
if state == 'collapsed' then
:tag('td')
:addClass('navbox-image')
subtable:addClass('mw-collapsed')
end
:addClass(args.imageclass)
header:addClass('navbox-header-collapsible')
:css('width', '1px') -- Minimize width
:css('padding', '0px 2px 0px 0px')
:cssText(args.imageleftstyle)
:attr('rowspan', #listnums)
:tag('div')
:wikitext(processItem(args.imageleft))
end
end


if args.headerclass then headerrow:addClass( args.headerclass ) end
if args['group' .. listnum] then
if args.headerstyle then header:cssText( args.headerstyle ) end
local groupCell = row:tag('th')


headerrow:node(header)
-- id for aria-labelledby attribute, if lone group with no title or above
subtable:node(headerrow)
if listnum == 1 and not (args.title or args.above or args.group2) then
groupCell
:attr('id', mw.uri.anchorEncode(args.group1))
end


sections[num] = subtable
groupCell
activeSection = num
:attr('scope', 'row')
end
:addClass('navbox-group')
:addClass(args.groupclass)
:cssText(args.basestyle)
:css('width', args.groupwidth or '1%') -- If groupwidth not specified, minimize width


--- Processes a single list row
groupCell
--
:cssText(args.groupstyle)
-- @param num Number of the row to be processed
:cssText(args['group' .. listnum .. 'style'])
local function processList(num)
:wikitext(args['group' .. listnum])
if not args['list'..num] then return end
end


local listCell = row:tag('td')
local row = mw.html.create('tr'):addClass('navbox-row')


if args['group' .. listnum] then
if not hasrows and not activeSection then
_processImage(row, 'imageleft')
listCell
:addClass('navbox-list1')
else
listCell:attr('colspan', 2)
end
end


local listcell = mw.html.create('td'):addClass('navbox-list')
if not args.groupwidth then
local hlistcell = listcell:tag('div'):addClass('hlist')
listCell:css('width', '100%')
local data = args['list'..num]
if data:sub(1,1) == '*' then
-- Add newlines to support lists properly
hlistcell
:newline()
:wikitext( data )
:newline()
else
hlistcell:wikitext( data )
end
end


local altRow = _alternateRow()
local rowstyle -- usually nil so cssText(rowstyle) usually adds nothing
if index % 2 == 1 then
if altRow then
row:addClass( args.altrowclass or 'alt' )
rowstyle = args.oddstyle

local listclass = args.altlistclass or args.listclass or false
if listclass then listcell:addClass( listclass ) end

local liststyle = args.altliststyle or args.liststyle or false
if liststyle then listcell:cssText( liststyle ) end
else
else
if args.rowclass then row:addClass( args.rowclass ) end
rowstyle = args.evenstyle
if args.listclass then listcell:addClass( args.listclass ) end
if args.liststyle then listcell:cssText( args.liststyle ) end
end
end


local listText = args['list' .. listnum]
if args['group'..num] then
local groupcell = mw.html.create('th'):addClass('navbox-group'):attr('scope','row'):wikitext( args['group'..num] )
local oddEven = ODD_EVEN_MARKER
if listText:sub(1, 12) == '</div><table' then
-- Assume list text is for a subgroup navbox so no automatic striping for this row.
oddEven = listText:find('<th[^>]*"navbox%-title"') and RESTART_MARKER or 'odd'
end
listCell
:css('padding', '0px')
:cssText(args.liststyle)
:cssText(rowstyle)
:cssText(args['list' .. listnum .. 'style'])
:addClass('navbox-list')
:addClass('navbox-' .. oddEven)
:addClass(args.listclass)
:addClass(args['list' .. listnum .. 'class'])
:tag('div')
:css('padding', (index == 1 and args.list1padding) or args.listpadding or '0em 0.25em')
:wikitext(processItem(listText, args.nowrapitems))


if index == 1 and args.image then
if altRow then
local groupclass = args.altgroupclass or args.groupclass or false
row
if groupclass then groupcell:addClass( groupclass ) end
:tag('td')
:addClass('navbox-image')
local groupstyle = args.altgroupstyle or args.groupstyle or false
:addClass(args.imageclass)
if groupstyle then groupcell:cssText( groupstyle ) end
:css('width', '1px') -- Minimize width
else
:css('padding', '0px 0px 0px 2px')
if args.groupclass then groupcell:addClass( args.groupclass ) end
:cssText(args.imagestyle)
if args.groupstyle then groupcell:cssText( args.groupstyle ) end
:attr('rowspan', #listnums)
end
:tag('div')

:wikitext(processItem(args.image))
row:node( groupcell )
else
listcell:attr('colspan',2):addClass('no-group')
end
end
end


row:node( listcell )


local firstRow = false
--
if not hasrows and not activeSection then
-- Tracking categories
firstRow = true
--
hasrows = true

_processImage(row, 'image')
local function needsHorizontalLists()
if border == 'subgroup' or args.tracking == 'no' then
return false
end
end
local listClasses = {
['plainlist'] = true, ['hlist'] = true, ['hlist hnum'] = true,
['hlist hwrap'] = true, ['hlist vcard'] = true, ['vcard hlist'] = true,
['hlist vevent'] = true,
}
return not (listClasses[args.listclass] or listClasses[args.bodyclass])
end


if activeSection then
-- there are a lot of list classes in the wild, so we have a function to find
local parent = sections[activeSection]
-- them and add their TemplateStyles
if not isChild or not firstRow then
local function addListStyles()
_addGutter(parent)
local frame = mw.getCurrentFrame()
-- TODO?: Should maybe take a table of classes for e.g. hnum, hwrap as above
-- I'm going to do the stupid thing first though
-- Also not sure hnum and hwrap are going to live in the same TemplateStyles
-- as hlist
local function _addListStyles(htmlclass, templatestyles)
local class_args = { -- rough order of probability of use
'bodyclass', 'listclass', 'aboveclass', 'belowclass', 'titleclass',
'navboxclass', 'groupclass', 'titlegroupclass', 'imageclass'
}
local patterns = {
'^' .. htmlclass .. '$',
'%s' .. htmlclass .. '$',
'^' .. htmlclass .. '%s',
'%s' .. htmlclass .. '%s'
}
local found = false
for _, arg in ipairs(class_args) do
for _, pattern in ipairs(patterns) do
if mw.ustring.find(args[arg] or '', pattern) then
found = true
break
end
end
if found then break end
end
end
parent:node(row)
if found then
hasData = true
return frame:extensionTag{
else
name = 'templatestyles', args = { src = templatestyles }
if not isChild or not firstRow then
}
_addGutter(navbox,not firstRow)
else
return ''
end
end
navbox:node( row )
rowspan = rowspan + 1
end
end
local hlist_styles = ''
-- navbar always has mini = 1, so here (on this wiki) we can assume that
-- we don't need to output hlist styles in navbox again.
if not has_navbar() then
hlist_styles = _addListStyles('hlist', 'Flatlist/styles.css')
end
local plainlist_styles = _addListStyles('plainlist', 'Plainlist/styles.css')
return hlist_styles .. plainlist_styles
end
end


--- Processes all rows
local function hasBackgroundColors()
local function processRows()
for _, key in ipairs({'titlestyle', 'groupstyle', 'basestyle', 'abovestyle', 'belowstyle'}) do
sections = {}
if tostring(args[key]):find('background', 1, true) then
for i=1,#rownums do
return true
local num = rownums[i]
if not skiprows[num] then
processHeader(num)
processList(num)
end
end
end
_closeCurrentSection()
if cimageleft then
cimageleft:attr('rowspan',rowspan)
end
if cimage then
cimage:attr('rowspan',rowspan)
end
end
end
end


------------------------------------------------
local function hasBorders()
-- ARGUMENTS PREPROCESSOR
for _, key in ipairs({'groupstyle', 'basestyle', 'abovestyle', 'belowstyle'}) do
-- * Extracts arguments from frame and stores them in args table
if tostring(args[key]):find('border', 1, true) then
-- * At the same time, checks for valid row numbers
return true
------------------------------------------------
end

--- Preprocessor for the arguments.
-- Will fill up the args table with the parameters from the frame grouped by their type.
--
-- @param frame The frame passed to the Module.
local function preProcessArgs(frame)
local tmp = {}

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


-- Storage tables
local function isIllegible()
local nums = {}
-- require('Module:Color contrast') absent on mediawiki.org
return false
end


-- Loop over all the args
local function getTrackingCategories()
for k,v in pairs(tmp) do
local cats = {}
-- Skip empty args, which are useless
if needsHorizontalLists() then table.insert(cats, 'Navigational boxes without horizontal lists') end
if v ~= '' then
if hasBackgroundColors() then table.insert(cats, 'Navboxes using background colours') end
local cat,num = tostring(k):match('^(%a+)([1-9]%d*)$')
if isIllegible() then table.insert(cats, 'Potentially illegible navboxes') end
if hasBorders() then table.insert(cats, 'Navboxes using borders') end
return cats
end


if cat == 'header' or cat == 'list' then
local function renderTrackingCategories(builder)
nums[num] = true
local title = mw.title.getCurrentTitle()
end
if title.namespace ~= 10 then return end -- not in template space
local subpage = title.subpageText
if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end


args[k] = v -- Simple copy
for _, cat in ipairs(getTrackingCategories()) do
end
builder:wikitext('[[Category:' .. cat .. ']]')
end
end
end


colspan = args.image and 3 or 2
--
if args.imageleft then colspan = colspan + 1 end
-- Main navbox tables
rowspan = 0
--
local function renderMainTable()
local tbl = mw.html.create('table')
:addClass('nowraplinks')
:addClass(args.bodyclass)


if args.title and (args.state ~= 'plain' and args.state ~= 'off') then
if args.alternaterows == 'swap' then
alt = true
if args.state == 'collapsed' then args.state = 'mw-collapsed' end
tbl
:addClass('mw-collapsible')
:addClass(args.state or 'autocollapse')
end
end


for k, v in pairs(nums) do
tbl:css('border-spacing', 0)
rownums[#rownums+1] = tonumber(k)
if border == 'subgroup' or border == 'none' then
tbl
:addClass('navbox-subgroup')
:cssText(args.bodystyle)
:cssText(args.style)
else -- regular navbox - bodystyle and style will be applied to the wrapper table
tbl
:addClass('navbox-inner')
:css('background', 'transparent')
:css('color', 'inherit')
end
end
tbl:cssText(args.innerstyle)


table.sort(rownums)
renderTitleRow(tbl)
renderAboveRow(tbl)
for i, listnum in ipairs(listnums) do
renderListRow(tbl, i, listnum)
end
renderBelowRow(tbl)


-- Calculate skip rows
return tbl
local cSection, cSkip
local showall = args.showall
for i=1,#rownums do
local num = rownums[i]
if args['header'..num] then
cSection = true
cSkip = false
local showme = args['show'..num]
if showme == 'no' then
cSkip = true
elseif showme == 'auto' or (showme ~= 'yes' and showall ~= 'yes') then
if not args['list'..num] then
local nextNum = rownums[i+1]
cSkip = not nextNum or args['header'..nextNum] -- If next has a header -> skip
end
end
end
if cSection and cSkip then
skiprows[num] = true
end
end
end
end


------------------------------------------------
function p._navbox(navboxArgs)
-- MAIN FUNCTIONS
args = navboxArgs
------------------------------------------------
listnums = {}


--- Processes the arguments to create the navbox.
for k, _ in pairs(args) do
--
if type(k) == 'string' then
-- @return A string with HTML that is the navbox.
local listnum = k:match('^list(%d+)$')
local function _navbox()
if listnum then table.insert(listnums, tonumber(listnum)) end
-- Create the root HTML element
end
local trim = function(s)
return s and mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1") or ''
end
end
local border = args.border or trim(args[1]) or ''
table.sort(listnums)
isChild = (border == 'child' or border == 'subgroup')


if isChild then
border = mw.text.trim(args.border or args[1] or '')
navbox = mw.html.create('table'):addClass('navbox-subgroup')
if border == 'child' then
else
border = 'subgroup'
navbox = mw.html.create('table'):addClass('navbox')
end


if args.state ~= 'plain' then
-- render the main body of the navbox
navbox:addClass('mw-collapsible'):attr('data-expandtext',args['expandtext'] or args['defaultexpandtext'] or showText):attr('data-collapsetext',args['collapsetext'] or args['defaultcollapsetext'] or hideText)
local tbl = renderMainTable()
if args.state == 'collapsed' then
navbox:addClass('mw-collapsed')
-- get templatestyles
end
local frame = mw.getCurrentFrame()
local base_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = 'Module:Navbox/styles.css' }
}
local templatestyles = ''
if args.templatestyles and args.templatestyles ~= '' then
templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args.templatestyles }
}
end
local res = mw.html.create()
-- 'navbox-styles' exists for two reasons:
-- 1. To wrap the styles to work around phab: T200206 more elegantly. Instead
-- of combinatorial rules, this ends up being linear number of CSS rules.
-- 2. To allow MobileFrontend to rip the styles out with 'nomobile' such that
-- they are not dumped into the mobile view.
res:tag('div')
:addClass('navbox-styles')
:addClass('nomobile')
:wikitext(base_templatestyles .. templatestyles)
:done()
-- render the appropriate wrapper around the navbox, depending on the border param
if border == 'none' then
local nav = res:tag('div')
:attr('role', 'navigation')
:wikitext(addListStyles())
:node(tbl)
-- aria-labelledby title, otherwise above, otherwise lone group
if args.title or args.above or (args.group1 and not args.group2) then
nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title or args.above or args.group1))
else
nav:attr('aria-label', 'Navbox')
end
elseif border == 'subgroup' then
-- We assume that this navbox is being rendered in a list cell of a
-- parent navbox, and is therefore inside a div with padding:0em 0.25em.
-- We start with a </div> to avoid the padding being applied, and at the
-- end add a <div> to balance out the parent's </div>
res
:wikitext('</div>')
:wikitext(addListStyles())
:node(tbl)
:wikitext('<div>')
else
local nav = res:tag('div')
:attr('role', 'navigation')
:addClass('navbox')
:addClass(args.navboxclass)
:cssText(args.bodystyle)
:cssText(args.style)
:css('padding', '3px')
:wikitext(addListStyles())
:node(tbl)
-- aria-labelledby title, otherwise above, otherwise lone group
if args.title or args.above or (args.group1 and not args.group2) then
nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title or args.above or args.group1))
else
nav:attr('aria-label', 'Navbox')
end
end
end
end


if args.bodyclass then navbox:addClass(args.bodyclass) end
if (args.nocat or 'false'):lower() == 'false' then
if args.bodystyle then navbox:cssText(args.bodystyle) end
renderTrackingCategories(res)
end


-- Process...
return striped(tostring(res))
if not isChild then
end
processTitle()
processAboveBelow('above')
processRows()
processAboveBelow('below')


return tostring(navbox)
function p.navbox(frame)
else
if not getArgs then
processRows()
getArgs = require('Module:Arguments').getArgs
end
args = getArgs(frame, {wrappers = {'Template:Navbox', 'Template:Navbox subgroup'}})
if frame.args.border then
-- This allows Template:Navbox_subgroup to use {{#invoke:Navbox|navbox|border=...}}.
args.border = frame.args.border
end


local wrapper = mw.html.create('')
-- Read the arguments in the order they'll be output in, to make references number in the right order.
wrapper:wikitext('</div>')
local _
wrapper:node(navbox)
_ = args.title
wrapper:wikitext('<div class="hlist">')
_ = args.above
return tostring(wrapper)
for i = 1, 20 do
_ = args["group" .. tostring(i)]
_ = args["list" .. tostring(i)]
end
end
end
_ = args.below


--- Main module entry point.
return p._navbox(args)
-- To be called with {{#invoke:navbox|main}} or directly from another module.
--
-- @param frame The frame passed to the module via the #invoke. If called from another
-- module directly, this should be a table with the parameter definition.
function p.main(frame)
-- Save the arguments in a local variable so other functions can use them.
preProcessArgs(frame)
return _navbox()
end
end



Revision as of 00:37, 31 August 2021

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

--------------------------------------------------------------------
--<pre> Navbox Module
--
-- * Fully CSS styled (inline styles possible but not default)
-- * Supports unlimited rows
--
-- By User:Tjcool007 from layton.fandom.com
--------------------------------------------------------------------

local p = {}

local args = {} -- Arguments passed to template
local navbox -- Actual navbox

--local working = {}
local rownums, skiprows = {}, {}
local hasrows, alt, hasData, isChild = false, false, false, false
local activeSection, sections, cimage, cimageleft
local colspan, rowspan

local showText, hideText = 'Show', 'Hide'

local langCode = mw.getContentLanguage():getCode()
local localization = {} --localized strings table
localization['bn'] = {show = 'দেখান', hide = 'লুকান'}
localization['de'] = {show = 'Ausklappen', hide = 'Einklappen'}
localization['en'] = {show = 'Show', hide = 'Hide'}
localization['es'] = {show = 'Mostrar', hide = 'Ocultar'}
localization['hi'] = {show = 'दिखाएँ', hide = 'छिपाएँ'}
localization['ja'] = {show = '表示', hide = '隠す'}
localization['ru'] = {show = 'показать', hide = 'скрыть'}
localization['zh'] = {show = '显示', hide = '隐藏'}
if localization[langCode] then
    showText = localization[langCode]['show']
    hideText = localization[langCode]['hide']
end

------------------------------------------------
-- Title
------------------------------------------------

--- Processes the VDE links in the title
--
-- @param titlecell The table cell of the title
local function processVde( titlecell )
	if not args.template then return end

	titlecell:wikitext('<span class="navbox-vde">'
		.. mw.getCurrentFrame():expandTemplate({
			title = 'vdelinks',
			args = { args.template, ['type'] = 'navbox' }
		}) .. '</span>')
end

--- Processes the main title row
local function processTitle()
	local titlerow = mw.html.create('tr'):addClass('navbox-title')
	local titlecell = mw.html.create('th'):attr('colspan',colspan):attr('scope','col')

	if not pcall( processVde, titlecell ) then
		titlecell:wikitext( '<b class="navbox-vde error" title="Missing Template:Vdelinks">!!!</b>' )
	end

	titlecell:wikitext( args.title or '{{{title}}}' )

	-- Padding
	local hasTemplate = args.template ~= nil
	local hasState = not args.state or args.state ~= 'plain'

	if hasTemplate ~= hasState then
		if hasTemplate then
			titlecell:addClass('navbox-title-padright')
		else
			titlecell:addClass('navbox-title-padleft')
		end
	end

	if args.titleclass then titlerow:addClass( args.titleclass ) end
	if args.titlestyle then titlecell:cssText( args.titlestyle ) end

	titlerow:node(titlecell)
	navbox:node(titlerow)
end

local function _addGutter( parent, incRowspan )
	parent:tag('tr'):addClass('navbox-gutter'):tag('td'):attr('colspan',2)

	if incRowspan then
		rowspan = rowspan + 1
	end
end

------------------------------------------------
-- Above/Below
------------------------------------------------

--- Processes the above and below rows
--
-- @param rowtype Either 'above' or 'below'
local function processAboveBelow( rowtype )
	if not args[rowtype] then return end

	local abrow = mw.html.create('tr'):addClass('navbox-'..rowtype)
	local abcell = mw.html.create('td'):attr('colspan',colspan):wikitext( args[rowtype] )

	if args[rowtype .. 'class'] then abrow:addClass( args[rowtype .. 'class'] ) end
	if args[rowtype .. 'style'] then abcell:cssText( args[rowtype .. 'style'] ) end

	abrow:node( abcell )
	_addGutter( navbox )
	navbox:node( abrow )
end

------------------------------------------------
-- Main Rows
------------------------------------------------

--- Processes the images
local function _processImage(row, imgtype)
	if not args[imgtype] then return end

	local iclass = imgtype == 'image' and 'navbox-image-right' or 'navbox-image-left'

	local imagecell = mw.html.create('td'):addClass('navbox-image'):addClass(iclass)

	local image = args[imgtype]
	if image:sub(1,1) ~= '[' then
		local width = args[imgtype .. 'width'] or '100px'
		imagecell:css('width',width):wikitext('['..'[' .. image  .. '|' .. width .. '|link=' .. (args[imgtype .. 'link'] or '') .. ']]')
	else
		imagecell:css('width','0%'):wikitext(image)
	end

	if args[imgtype .. 'class'] then imagecell:addClass( args[imgtype .. 'class'] ) end
	if args[imgtype .. 'style'] then imagecell:cssText( args[imgtype .. 'style'] ) end

	row:node( imagecell )
	if imgtype == 'image' then
		cimage = imagecell
	else
		cimageleft = imagecell
	end
end

--- Closes the currently active section (if any)
local function _closeCurrentSection()
	if not activeSection then return end

	local row = mw.html.create('tr'):addClass('navbox-section-row')
	local cell = mw.html.create('td'):attr('colspan',2)

	if not hasrows then
		_processImage(row,'imageleft')	
	end

	cell:node(sections[activeSection])
	row:node(cell)

	local firstRow = false
	if not hasrows then
		firstRow = true
		hasrows = true
		_processImage(row,'image')	
	end

	_addGutter(navbox,not firstRow)
	navbox:node(row)	
	rowspan = rowspan + 1

	activeSection = false
	hasData = false
end

--- Handles alternating rows
--
-- @return Alternatingly returns true or false. Always returns false if alternating rows
--         are disabled with "alternaterows = no"
local function _alternateRow()
	if args.alternaterows == 'no' then return false end
	if alt then
		alt = false
		return true
	else
		alt = true
		return false
	end
end

--- Process a single Header "row"
--
-- @param num Number of the row to be processed
local function processHeader(num)
	if not args['header'..num] then return end

	_closeCurrentSection()

	local subtable = mw.html.create('table'):addClass('navbox-section')
	local headerrow = mw.html.create('tr')
	local header = mw.html.create('th'):addClass('navbox-header'):attr('colspan',2):attr('scope','col'):wikitext( args['header'..num] )

	local collapseme = args['state'..num] or false
	local state = false

	if collapseme then
		-- Look at this one
		if collapseme ~= 'plain' then
			state = collapseme == 'expanded' and 'expanded' or 'collapsed'
		end
	else
		-- Look at default 
		local collapseall = args.defaultstate or false
		if collapseall then
			state = collapseall == 'expanded' and 'expanded' or 'collapsed'	
		end
	end

	if state then
		subtable:addClass('mw-collapsible'):attr('data-expandtext',args['expandtext'..num] or args['defaultexpandtext'] or showText):attr('data-collapsetext',args['collapsetext'..num] or args['defaultcollapsetext'] or hideText)
		if state == 'collapsed' then
			subtable:addClass('mw-collapsed')	
		end
		header:addClass('navbox-header-collapsible')
	end

	if args.headerclass then headerrow:addClass( args.headerclass ) end
	if args.headerstyle then header:cssText( args.headerstyle ) end

	headerrow:node(header)	
	subtable:node(headerrow)

	sections[num] = subtable
	activeSection = num
end

--- Processes a single list row
--
-- @param num Number of the row to be processed
local function processList(num)	
	if not args['list'..num] then return end

	local row = mw.html.create('tr'):addClass('navbox-row')

	if not hasrows and not activeSection then
		_processImage(row, 'imageleft')	
	end

	local listcell = mw.html.create('td'):addClass('navbox-list')
	local hlistcell = listcell:tag('div'):addClass('hlist')
	
	local data = args['list'..num]
	
	if data:sub(1,1) == '*' then
		-- Add newlines to support lists properly
		hlistcell
			:newline()
			:wikitext( data )
			:newline()
	else
		hlistcell:wikitext( data )
	end

	local altRow = _alternateRow()
	if altRow then
		row:addClass( args.altrowclass or 'alt' )

		local listclass = args.altlistclass or args.listclass or false
		if listclass then listcell:addClass( listclass ) end

		local liststyle = args.altliststyle or args.liststyle or false
		if liststyle then listcell:cssText( liststyle ) end
	else
		if args.rowclass then row:addClass( args.rowclass ) end
		if args.listclass then listcell:addClass( args.listclass ) end
		if args.liststyle then listcell:cssText( args.liststyle ) end
	end

	if args['group'..num] then
		local groupcell = mw.html.create('th'):addClass('navbox-group'):attr('scope','row'):wikitext( args['group'..num] )

		if altRow then
			local groupclass = args.altgroupclass or args.groupclass or false
			if groupclass then groupcell:addClass( groupclass ) end
 
			local groupstyle = args.altgroupstyle or args.groupstyle or false
			if groupstyle then groupcell:cssText( groupstyle ) end
		else	
			if args.groupclass then groupcell:addClass( args.groupclass ) end
			if args.groupstyle then groupcell:cssText( args.groupstyle ) end
		end

		row:node( groupcell )
	else
		listcell:attr('colspan',2):addClass('no-group')
	end

	row:node( listcell )

	local firstRow = false
	if not hasrows and not activeSection then
		firstRow = true
		hasrows = true
		_processImage(row, 'image')
	end

	if activeSection then
		local parent = sections[activeSection]
		if not isChild or not firstRow then
			_addGutter(parent)
		end
		parent:node(row)
		hasData = true
	else
		if not isChild or not firstRow then
			_addGutter(navbox,not firstRow)
		end
		navbox:node( row )
		rowspan = rowspan + 1
	end
end

--- Processes all rows
local function processRows()
	sections = {}
	for i=1,#rownums do
		local num = rownums[i]
		if not skiprows[num] then
			processHeader(num)
			processList(num)
		end
	end
	_closeCurrentSection()
 
	if cimageleft then
		cimageleft:attr('rowspan',rowspan)		
	end
	if cimage then
		cimage:attr('rowspan',rowspan)
	end
end

------------------------------------------------
-- ARGUMENTS PREPROCESSOR
-- * Extracts arguments from frame and stores them in args table
-- * At the same time, checks for valid row numbers
------------------------------------------------

--- Preprocessor for the arguments.
-- Will fill up the args table with the parameters from the frame grouped by their type.
--
-- @param frame The frame passed to the Module.
local function preProcessArgs(frame)
	local tmp = {}

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

	-- Storage tables
	local nums = {}

	-- Loop over all the args
	for k,v in pairs(tmp) do
		-- Skip empty args, which are useless
		if v ~= '' then
			local cat,num = tostring(k):match('^(%a+)([1-9]%d*)$')

			if cat == 'header' or cat == 'list' then
				nums[num] = true
			end

			args[k] = v -- Simple copy
		end
	end

	colspan = args.image and 3 or 2
	if args.imageleft then colspan = colspan + 1 end
	rowspan = 0

	if args.alternaterows == 'swap' then
		alt = true
	end

	for k, v in pairs(nums) do
		rownums[#rownums+1] = tonumber(k)
	end

	table.sort(rownums)

	-- Calculate skip rows
	local cSection, cSkip
	local showall = args.showall
	for i=1,#rownums do
		local num = rownums[i]
		if args['header'..num] then
			cSection = true
			cSkip = false
			local showme = args['show'..num]
			if showme == 'no' then
				cSkip = true
			elseif showme == 'auto' or (showme ~= 'yes' and showall ~= 'yes') then
				if not args['list'..num] then
					local nextNum = rownums[i+1]
					cSkip = not nextNum or args['header'..nextNum] -- If next has a header -> skip
				end
			end
		end
		if cSection and cSkip then
			skiprows[num] = true
		end
	end
end

------------------------------------------------
-- MAIN FUNCTIONS
------------------------------------------------

--- Processes the arguments to create the navbox.
--
-- @return A string with HTML that is the navbox.
local function _navbox()
	-- Create the root HTML element
	local trim = function(s)
		return s and mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1") or ''
	end
	local border = args.border or trim(args[1])  or ''
	isChild = (border == 'child' or border == 'subgroup')

	if isChild then
		navbox = mw.html.create('table'):addClass('navbox-subgroup')
	else
		navbox = mw.html.create('table'):addClass('navbox')

		if args.state ~= 'plain' then
			navbox:addClass('mw-collapsible'):attr('data-expandtext',args['expandtext'] or args['defaultexpandtext'] or showText):attr('data-collapsetext',args['collapsetext'] or args['defaultcollapsetext'] or hideText)
			if args.state == 'collapsed' then
				navbox:addClass('mw-collapsed')
			end
		end
	end

 	if args.bodyclass then navbox:addClass(args.bodyclass) end
	if args.bodystyle then navbox:cssText(args.bodystyle) end

	-- Process...
	if not isChild then
		processTitle()
		processAboveBelow('above')
		processRows()
		processAboveBelow('below')

		return tostring(navbox)
	else
		processRows()

		local wrapper = mw.html.create('')
		wrapper:wikitext('</div>')
		wrapper:node(navbox)
		wrapper:wikitext('<div class="hlist">')
		return tostring(wrapper)
	end
end

--- Main module entry point.
-- To be called with {{#invoke:navbox|main}} or directly from another module.
--
-- @param frame The frame passed to the module via the #invoke. If called from another
--              module directly, this should be a table with the parameter definition.
function p.main(frame)
	-- Save the arguments in a local variable so other functions can use them.
	preProcessArgs(frame)
 
	return _navbox()
end

return p