Module:Navbox: Difference between revisions
From Podpedia
Content added Content deleted
Line 1: | Line 1: | ||
-------------------------------------------------------------------- |
|||
-- Navbox Module |
|||
-- |
-- |
||
-- This module implements {{Navbox}} |
|||
-- * Fully CSS styled (inline styles possible but not default) |
|||
-- * Supports unlimited rows |
|||
-- |
-- |
||
-- By User:Tjcool007 from layton.wikia.com |
|||
-------------------------------------------------------------------- |
|||
local p = {} |
local p = {} |
||
local args = {} -- Arguments passed to template |
|||
local navbox -- Actual navbox |
|||
local navbar = require('Module:Navbar')._navbar |
|||
local rownums, skiprows = {}, {} |
|||
local getArgs -- lazily initialized |
|||
local hasrows, alt, hasData, isChild = false, false, false, false |
|||
local activeSection, sections, cimage, cimageleft |
|||
local |
local args |
||
local border |
|||
local |
local listnums = {} |
||
local ODD_EVEN_MARKER = '\127_ODDEVEN_\127' |
|||
local RESTART_MARKER = '\127_ODDEVEN0_\127' |
|||
------------------------------------------------ |
|||
local REGEX_MARKER = '\127_ODDEVEN(%d?)_\127' |
|||
-- Title |
|||
------------------------------------------------ |
|||
local function striped(wikitext) |
|||
-- Return wikitext with markers replaced for odd/even striping. |
|||
--- Processes the VDE links in the title |
|||
-- Child (subgroup) navboxes are flagged with a category that is removed |
|||
-- |
|||
-- |
-- by parent navboxes. The result is that the category shows all pages |
||
-- where a child navbox is not contained in a parent navbox. |
|||
local function processVde( titlecell ) |
|||
local orphanCat = '[[Category:Navbox orphans]]' |
|||
if not args.template then return end |
|||
if border == 'subgroup' and args.orphan ~= 'yes' then |
|||
-- No change; striping occurs in outermost navbox. |
|||
titlecell:wikitext('<span class="navbox-vde">' |
|||
return wikitext .. orphanCat |
|||
.. 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' |
|||
if args.evenodd then |
|||
titlecell:wikitext( args.title or '{{{title}}}' ) |
|||
if args.evenodd == 'swap' then |
|||
first, second = second, first |
|||
-- 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 |
else |
||
first = args.evenodd |
|||
titlecell:addClass('navbox-title-padleft') |
|||
second = first |
|||
end |
end |
||
end |
end |
||
local changer |
|||
if first == second then |
|||
if args.titleclass then titlerow:addClass( args.titleclass ) end |
|||
changer = first |
|||
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' |
|||
else |
else |
||
local index = 0 |
|||
imagecell:css('width','0%'):wikitext(image) |
|||
changer = function (code) |
|||
end |
|||
if code == '0' then |
|||
-- Current occurrence is for a group before a nested table. |
|||
if args[imgtype .. 'class'] then imagecell:addClass( args[imgtype .. 'class'] ) end |
|||
-- Set it to first as a valid although pointless class. |
|||
if args[imgtype .. 'style'] then imagecell:cssText( args[imgtype .. 'style'] ) end |
|||
-- The next occurrence will be the first row after a title |
|||
-- in a subgroup and will also be first. |
|||
row:node( imagecell ) |
|||
index = 0 |
|||
if imgtype == 'image' then |
|||
return first |
|||
cimage = imagecell |
|||
end |
|||
else |
|||
index = index + 1 |
|||
return index % 2 == 1 and first or second |
|||
end |
|||
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) |
|||
--- Closes the currently active section (if any) |
|||
if item:sub(1, 2) == '{|' then |
|||
local function _closeCurrentSection() |
|||
-- Applying nowrap to lines in a table does not make sense. |
|||
if not activeSection then return end |
|||
-- Add newlines to compensate for trim of x in |parm=x in a template. |
|||
return '\n' .. item ..'\n' |
|||
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 |
end |
||
if nowrapitems == 'yes' then |
|||
local lines = {} |
|||
cell:node(sections[activeSection]) |
|||
for line in (item .. '\n'):gmatch('([^\n]*)\n') do |
|||
row:node(cell) |
|||
local prefix, content = line:match('^([*:;#]+)%s*(.*)') |
|||
if prefix and not content:match('^<span class="nowrap">') then |
|||
local firstRow = false |
|||
line = prefix .. '<span class="nowrap">' .. content .. '</span>' |
|||
if not hasrows then |
|||
end |
|||
firstRow = true |
|||
table.insert(lines, line) |
|||
hasrows = true |
|||
end |
|||
_processImage(row,'image') |
|||
item = table.concat(lines, '\n') |
|||
end |
end |
||
if item:match('^[*:;#]') then |
|||
return '\n' .. item ..'\n' |
|||
_addGutter(navbox,not firstRow) |
|||
end |
|||
navbox:node(row) |
|||
return item |
|||
rowspan = rowspan + 1 |
|||
activeSection = false |
|||
hasData = false |
|||
end |
end |
||
local function renderNavBar(titleCell) |
|||
--- Handles alternating rows |
|||
-- |
|||
if args.navbar ~= 'off' and args.navbar ~= 'plain' and not (not args.name and mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') == 'Template:Navbox') then |
|||
-- @return Alternatingly returns true or false. Always returns false if alternating rows |
|||
titleCell:wikitext(navbar{ |
|||
-- are disabled with "alternaterows = no" |
|||
args.name, |
|||
local function _alternateRow() |
|||
mini = 1, |
|||
if args.alternaterows == 'no' then return false end |
|||
fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;' |
|||
if alt then |
|||
}) |
|||
alt = false |
|||
return true |
|||
else |
|||
alt = true |
|||
return false |
|||
end |
end |
||
end |
end |
||
--- Process a single Header "row" |
|||
-- |
-- |
||
-- Title row |
|||
-- @param num Number of the row to be processed |
|||
-- |
|||
local function processHeader(num) |
|||
local function renderTitleRow(tbl) |
|||
if not args['header'..num] then return end |
|||
if not args.title then return end |
|||
_closeCurrentSection() |
|||
local titleRow = tbl:tag('tr') |
|||
local subtable = mw.html.create('table'):addClass('navbox-section') |
|||
if args.titlegroup then |
|||
local headerrow = mw.html.create('tr') |
|||
titleRow |
|||
local header = mw.html.create('th'):addClass('navbox-header'):attr('colspan',2):attr('scope','col'):wikitext( args['header'..num] ) |
|||
:tag('th') |
|||
:attr('scope', 'row') |
|||
local collapseme = args['state'..num] or false |
|||
:addClass('navbox-group') |
|||
local state = false |
|||
:addClass(args.titlegroupclass) |
|||
:cssText(args.basestyle) |
|||
if collapseme then |
|||
:cssText(args.groupstyle) |
|||
-- Look at this one |
|||
:cssText(args.titlegroupstyle) |
|||
if collapseme ~= 'plain' then |
|||
:wikitext(args.titlegroup) |
|||
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 |
end |
||
local titleCell = titleRow:tag('th'):attr('scope', 'col') |
|||
if state then |
|||
subtable:addClass('mw-collapsible'):attr('data-expandtext',showText):attr('data-collapsetext',hideText) |
|||
if args.titlegroup then |
|||
titleCell |
|||
subtable:addClass('mw-collapsed') |
|||
:css('border-left', '2px solid #fdfdfd') |
|||
end |
|||
:css('width', '100%') |
|||
header:addClass('navbox-header-collapsible') |
|||
end |
end |
||
local titleColspan = 2 |
|||
if args.headerclass then headerrow:addClass( args.headerclass ) end |
|||
if args. |
if args.imageleft then titleColspan = titleColspan + 1 end |
||
if args.image then titleColspan = titleColspan + 1 end |
|||
if args.titlegroup then titleColspan = titleColspan - 1 end |
|||
headerrow:node(header) |
|||
subtable:node(headerrow) |
|||
titleCell |
|||
:cssText(args.basestyle) |
|||
sections[num] = subtable |
|||
:cssText(args.titlestyle) |
|||
activeSection = num |
|||
:addClass('navbox-title') |
|||
:attr('colspan', titleColspan) |
|||
renderNavBar(titleCell) |
|||
titleCell |
|||
:tag('div') |
|||
:attr('id', mw.uri.anchorEncode(args.title)) |
|||
:addClass(args.titleclass) |
|||
:css('font-size', '114%') |
|||
:css('margin', '0 4em') |
|||
:wikitext(processItem(args.title)) |
|||
end |
end |
||
--- Processes a single list row |
|||
-- |
-- |
||
-- Above/Below rows |
|||
-- @param num Number of the row to be processed |
|||
-- |
|||
local function processList(num) |
|||
if not args['list'..num] then return end |
|||
local function getAboveBelowColspan() |
|||
local ret = 2 |
|||
local row = mw.html.create('tr'):addClass('navbox-row') |
|||
if args.imageleft then ret = ret + 1 end |
|||
if args.image then ret = ret + 1 end |
|||
if not hasrows and not activeSection then |
|||
return ret |
|||
_processImage(row, 'imageleft') |
|||
end |
|||
local function renderAboveRow(tbl) |
|||
if not args.above then return end |
|||
tbl:tag('tr') |
|||
:tag('td') |
|||
:addClass('navbox-abovebelow') |
|||
:addClass(args.aboveclass) |
|||
:cssText(args.basestyle) |
|||
:cssText(args.abovestyle) |
|||
:attr('colspan', getAboveBelowColspan()) |
|||
:tag('div') |
|||
:wikitext(processItem(args.above, args.nowrapitems)) |
|||
end |
|||
local function renderBelowRow(tbl) |
|||
if not args.below then return end |
|||
tbl:tag('tr') |
|||
: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 |
|||
-- |
|||
-- List rows |
|||
-- |
|||
local function renderListRow(tbl, index, listnum) |
|||
local row = tbl:tag('tr') |
|||
if index == 1 and args.imageleft then |
|||
row |
|||
:tag('td') |
|||
:addClass('navbox-image') |
|||
:addClass(args.imageclass) |
|||
: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['group' .. listnum] then |
|||
local listcell = mw.html.create('td'):addClass('navbox-list') |
|||
local groupCell = row:tag('th') |
|||
groupCell |
|||
local data = args['list'..num] |
|||
:attr('scope', 'row') |
|||
:addClass('navbox-group') |
|||
if data:sub(1,1) == '*' then |
|||
:addClass(args.groupclass) |
|||
-- Add newlines to support lists properly |
|||
:cssText(args.basestyle) |
|||
hlistcell |
|||
:css('width', args.groupwidth or '1%') -- If groupwidth not specified, minimize width |
|||
:newline() |
|||
:wikitext( data ) |
|||
groupCell |
|||
:newline() |
|||
:cssText(args.groupstyle) |
|||
else |
|||
:cssText(args['group' .. listnum .. 'style']) |
|||
hlistcell:wikitext( data ) |
|||
:wikitext(args['group' .. listnum]) |
|||
end |
end |
||
local |
local listCell = row:tag('td') |
||
if altRow then |
|||
if args['group' .. listnum] then |
|||
row:addClass( args.altrowclass or 'alt' ) |
|||
listCell |
|||
:css('text-align', 'left') |
|||
local listclass = args.altlistclass or args.listclass or false |
|||
:css('border-left-width', '2px') |
|||
if listclass then listcell:addClass( listclass ) end |
|||
:css('border-left-style', 'solid') |
|||
local liststyle = args.altliststyle or args.liststyle or false |
|||
if liststyle then listcell:cssText( liststyle ) end |
|||
else |
else |
||
listCell:attr('colspan', 2) |
|||
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 |
end |
||
if args |
if not args.groupwidth then |
||
listCell:css('width', '100%') |
|||
local groupcell = mw.html.create('th'):addClass('navbox-group'):attr('scope','row'):wikitext( args['group'..num] ) |
|||
end |
|||
if altRow then |
|||
local rowstyle -- usually nil so cssText(rowstyle) usually adds nothing |
|||
local groupclass = args.altgroupclass or args.groupclass or false |
|||
if index % 2 == 1 then |
|||
if groupclass then groupcell:addClass( groupclass ) end |
|||
rowstyle = args.oddstyle |
|||
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 |
else |
||
rowstyle = args.evenstyle |
|||
listcell:attr('colspan',2):addClass('no-group') |
|||
end |
end |
||
local listText = args['list' .. listnum] |
|||
row:node( listcell ) |
|||
local oddEven = ODD_EVEN_MARKER |
|||
if listText:sub(1, 12) == '</div><table' then |
|||
local firstRow = false |
|||
-- Assume list text is for a subgroup navbox so no automatic striping for this row. |
|||
if not hasrows and not activeSection then |
|||
oddEven = listText:find('<th[^>]*"navbox%-title"') and RESTART_MARKER or 'odd' |
|||
firstRow = true |
|||
hasrows = true |
|||
_processImage(row, 'image') |
|||
end |
end |
||
listCell |
|||
:css('padding', '0px') |
|||
if activeSection then |
|||
:cssText(args.liststyle) |
|||
local parent = sections[activeSection] |
|||
:cssText(rowstyle) |
|||
if not isChild or not firstRow then |
|||
:cssText(args['list' .. listnum .. 'style']) |
|||
_addGutter(parent) |
|||
:addClass('navbox-list') |
|||
:addClass('navbox-' .. oddEven) |
|||
:addClass(args.listclass) |
|||
: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 |
|||
row |
|||
:tag('td') |
|||
:addClass('navbox-image') |
|||
:addClass(args.imageclass) |
|||
:css('width', '1px') -- Minimize width |
|||
:css('padding', '0px 0px 0px 2px') |
|||
:cssText(args.imagestyle) |
|||
:attr('rowspan', #listnums) |
|||
:tag('div') |
|||
:wikitext(processItem(args.image)) |
|||
end |
|||
end |
|||
-- |
|||
-- Tracking categories |
|||
-- |
|||
local function needsHorizontalLists() |
|||
if border == 'subgroup' or args.tracking == 'no' then |
|||
return false |
|||
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 |
|||
local function hasBackgroundColors() |
|||
for _, key in ipairs({'titlestyle', 'groupstyle', 'basestyle'}) do |
|||
if tostring(args[key]):find('background', 1, true) then |
|||
return true |
|||
end |
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 |
||
end |
end |
||
local function isIllegible() |
|||
--- Processes all rows |
|||
local styleratio = require('Module:Color contrast')._styleratio |
|||
local function processRows() |
|||
sections = {} |
|||
for |
for key, style in pairs(args) do |
||
if tostring(key):match("style$") then |
|||
local num = rownums[i] |
|||
if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then |
|||
if not skiprows[num] then |
|||
return true |
|||
processHeader(num) |
|||
end |
|||
processList(num) |
|||
end |
end |
||
end |
end |
||
return false |
|||
_closeCurrentSection() |
|||
end |
|||
if cimageleft then |
|||
local function getTrackingCategories() |
|||
cimageleft:attr('rowspan',rowspan) |
|||
local cats = {} |
|||
end |
|||
if needsHorizontalLists() then table.insert(cats, 'Navigational boxes without horizontal lists') end |
|||
if cimage then |
|||
if hasBackgroundColors() then table.insert(cats, 'Navboxes using background colours') end |
|||
cimage:attr('rowspan',rowspan) |
|||
if isIllegible() then table.insert(cats, 'Potentially illegible navboxes') end |
|||
return cats |
|||
end |
|||
local function renderTrackingCategories(builder) |
|||
local title = mw.title.getCurrentTitle() |
|||
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 |
|||
for _, cat in ipairs(getTrackingCategories()) do |
|||
builder:wikitext('[[Category:' .. cat .. ']]') |
|||
end |
end |
||
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. |
|||
-- |
-- |
||
-- Main navbox tables |
|||
-- @param frame The frame passed to the Module. |
|||
-- |
|||
local function preProcessArgs(frame) |
|||
local function renderMainTable() |
|||
local tbl = mw.html.create('table') |
|||
:addClass('nowraplinks') |
|||
if frame == mw.getCurrentFrame() then |
|||
:addClass(args.bodyclass) |
|||
tmp = frame:getParent().args |
|||
else |
|||
if args.title and (args.state ~= 'plain' and args.state ~= 'off') then |
|||
tmp = frame |
|||
tbl |
|||
:addClass('collapsible') |
|||
:addClass(args.state or 'autocollapse') |
|||
end |
end |
||
tbl:css('border-spacing', 0) |
|||
-- Storage tables |
|||
if border == 'subgroup' or border == 'none' then |
|||
local nums = {} |
|||
tbl |
|||
:addClass('navbox-subgroup') |
|||
-- Loop over all the args |
|||
:cssText(args.bodystyle) |
|||
for k,v in pairs(tmp) do |
|||
:cssText(args.style) |
|||
-- Skip empty args, which are useless |
|||
else -- regular navbox - bodystyle and style will be applied to the wrapper table |
|||
if v ~= '' then |
|||
tbl |
|||
local cat,num = tostring(k):match('^(%a+)([1-9]%d*)$') |
|||
:addClass('navbox-inner') |
|||
:css('background', 'transparent') |
|||
if cat == 'header' or cat == 'list' then |
|||
:css('color', 'inherit') |
|||
nums[num] = true |
|||
end |
|||
args[k] = v -- Simple copy |
|||
end |
|||
end |
end |
||
tbl:cssText(args.innerstyle) |
|||
colspan = args.image and 3 or 2 |
|||
renderTitleRow(tbl) |
|||
if args.imageleft then colspan = colspan + 1 end |
|||
renderAboveRow(tbl) |
|||
rowspan = 0 |
|||
for i, listnum in ipairs(listnums) do |
|||
renderListRow(tbl, i, listnum) |
|||
if args.alternaterows == 'swap' then |
|||
alt = true |
|||
end |
end |
||
renderBelowRow(tbl) |
|||
return tbl |
|||
if not args.template and args.name ~= nil then |
|||
end |
|||
args.template = args.name |
|||
function p._navbox(navboxArgs) |
|||
args = navboxArgs |
|||
for k, _ in pairs(args) do |
|||
if type(k) == 'string' then |
|||
local listnum = k:match('^list(%d+)$') |
|||
if listnum then table.insert(listnums, tonumber(listnum)) end |
|||
end |
|||
end |
end |
||
table.sort(listnums) |
|||
for k, v in pairs(nums) do |
|||
border = mw.text.trim(args.border or args[1] or '') |
|||
rownums[#rownums+1] = tonumber(k) |
|||
if border == 'child' then |
|||
border = 'subgroup' |
|||
end |
end |
||
-- render the main body of the navbox |
|||
table.sort(rownums) |
|||
local tbl = renderMainTable() |
|||
-- Calculate skip rows |
|||
-- render the appropriate wrapper around the navbox, depending on the border param |
|||
local cSection, cSkip |
|||
local |
local res = mw.html.create() |
||
if border == 'none' then |
|||
for i=1,#rownums do |
|||
local |
local nav = res:tag('div') |
||
:attr('role', 'navigation') |
|||
if args['header'..num] then |
|||
:node(tbl) |
|||
cSection = true |
|||
if args.title then |
|||
cSkip = false |
|||
nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title)) |
|||
local showme = args['show'..num] |
|||
else |
|||
if showme == 'no' then |
|||
nav:attr('aria-label', 'Navbox') |
|||
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 |
end |
||
elseif border == 'subgroup' then |
|||
-- We assume that this navbox is being rendered in a list cell of a parent navbox, and is |
|||
skiprows[num] = true |
|||
-- 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>') |
|||
:node(tbl) |
|||
:wikitext('<div>') |
|||
else |
|||
local nav = res:tag('div') |
|||
:attr('role', 'navigation') |
|||
:addClass('navbox') |
|||
:cssText(args.bodystyle) |
|||
:cssText(args.style) |
|||
:css('padding', '3px') |
|||
:node(tbl) |
|||
if args.title then |
|||
nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title)) |
|||
else |
|||
nav:attr('aria-label', 'Navbox') |
|||
end |
end |
||
end |
end |
||
renderTrackingCategories(res) |
|||
return striped(tostring(res)) |
|||
end |
end |
||
function p.navbox(frame) |
|||
------------------------------------------------ |
|||
if not getArgs then |
|||
-- MAIN FUNCTIONS |
|||
getArgs = require('Module:Arguments').getArgs |
|||
------------------------------------------------ |
|||
--- 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 |
end |
||
args = getArgs(frame, {wrappers = 'Template:Navbox'}) |
|||
local border = args.border or trim(args[1]) or '' |
|||
isChild = (border == 'child' or border == 'subgroup') |
|||
-- Read the arguments in the order they'll be output in, to make references number in the right order. |
|||
local _ |
|||
if isChild then |
|||
_ = args.title |
|||
navbox = mw.html.create('table'):addClass('navbox-subgroup') |
|||
_ = args.above |
|||
else |
|||
for i = 1, 20 do |
|||
navbox = mw.html.create('table'):addClass('navbox') |
|||
_ = args["group" .. tostring(i)] |
|||
_ = args["list" .. tostring(i)] |
|||
if args.state ~= 'plain' then |
|||
navbox:addClass('mw-collapsible'):attr('data-expandtext',showText):attr('data-collapsetext',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>') |
|||
return tostring(wrapper) |
|||
end |
end |
||
_ = args.below |
|||
return p._navbox(args) |
|||
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 |
return p |
||
-- [[Category:Modules]] |
Revision as of 19:04, 19 February 2018
Documentation for this module may be created at Module:Navbox/doc
--
-- This module implements {{Navbox}}
--
local p = {}
local navbar = require('Module:Navbar')._navbar
local getArgs -- lazily initialized
local args
local border
local listnums = {}
local ODD_EVEN_MARKER = '\127_ODDEVEN_\127'
local RESTART_MARKER = '\127_ODDEVEN0_\127'
local REGEX_MARKER = '\127_ODDEVEN(%d?)_\127'
local function striped(wikitext)
-- Return wikitext with markers replaced for odd/even striping.
-- Child (subgroup) navboxes are flagged with a category that is removed
-- by parent navboxes. The result is that the category shows all pages
-- where a child navbox is not contained in a parent navbox.
local orphanCat = '[[Category:Navbox orphans]]'
if border == 'subgroup' and args.orphan ~= 'yes' then
-- No change; striping occurs in outermost navbox.
return wikitext .. orphanCat
end
local first, second = 'odd', 'even'
if args.evenodd then
if args.evenodd == 'swap' then
first, second = second, first
else
first = args.evenodd
second = first
end
end
local changer
if first == second then
changer = first
else
local index = 0
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
local function processItem(item, nowrapitems)
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 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
local function renderNavBar(titleCell)
if args.navbar ~= 'off' and args.navbar ~= 'plain' and not (not args.name and mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') == 'Template:Navbox') then
titleCell:wikitext(navbar{
args.name,
mini = 1,
fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;'
})
end
end
--
-- Title row
--
local function renderTitleRow(tbl)
if not args.title then return end
local titleRow = tbl:tag('tr')
if args.titlegroup then
titleRow
:tag('th')
:attr('scope', 'row')
:addClass('navbox-group')
:addClass(args.titlegroupclass)
:cssText(args.basestyle)
:cssText(args.groupstyle)
:cssText(args.titlegroupstyle)
:wikitext(args.titlegroup)
end
local titleCell = titleRow:tag('th'):attr('scope', 'col')
if args.titlegroup then
titleCell
:css('border-left', '2px solid #fdfdfd')
:css('width', '100%')
end
local titleColspan = 2
if args.imageleft then titleColspan = titleColspan + 1 end
if args.image then titleColspan = titleColspan + 1 end
if args.titlegroup then titleColspan = titleColspan - 1 end
titleCell
:cssText(args.basestyle)
:cssText(args.titlestyle)
:addClass('navbox-title')
:attr('colspan', titleColspan)
renderNavBar(titleCell)
titleCell
:tag('div')
:attr('id', mw.uri.anchorEncode(args.title))
:addClass(args.titleclass)
:css('font-size', '114%')
:css('margin', '0 4em')
:wikitext(processItem(args.title))
end
--
-- Above/Below rows
--
local function getAboveBelowColspan()
local ret = 2
if args.imageleft then ret = ret + 1 end
if args.image then ret = ret + 1 end
return ret
end
local function renderAboveRow(tbl)
if not args.above then return end
tbl:tag('tr')
:tag('td')
:addClass('navbox-abovebelow')
:addClass(args.aboveclass)
:cssText(args.basestyle)
:cssText(args.abovestyle)
:attr('colspan', getAboveBelowColspan())
:tag('div')
:wikitext(processItem(args.above, args.nowrapitems))
end
local function renderBelowRow(tbl)
if not args.below then return end
tbl:tag('tr')
: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
--
-- List rows
--
local function renderListRow(tbl, index, listnum)
local row = tbl:tag('tr')
if index == 1 and args.imageleft then
row
:tag('td')
:addClass('navbox-image')
:addClass(args.imageclass)
:css('width', '1px') -- Minimize width
:css('padding', '0px 2px 0px 0px')
:cssText(args.imageleftstyle)
:attr('rowspan', #listnums)
:tag('div')
:wikitext(processItem(args.imageleft))
end
if args['group' .. listnum] then
local groupCell = row:tag('th')
groupCell
:attr('scope', 'row')
:addClass('navbox-group')
:addClass(args.groupclass)
:cssText(args.basestyle)
:css('width', args.groupwidth or '1%') -- If groupwidth not specified, minimize width
groupCell
:cssText(args.groupstyle)
:cssText(args['group' .. listnum .. 'style'])
:wikitext(args['group' .. listnum])
end
local listCell = row:tag('td')
if args['group' .. listnum] then
listCell
:css('text-align', 'left')
:css('border-left-width', '2px')
:css('border-left-style', 'solid')
else
listCell:attr('colspan', 2)
end
if not args.groupwidth then
listCell:css('width', '100%')
end
local rowstyle -- usually nil so cssText(rowstyle) usually adds nothing
if index % 2 == 1 then
rowstyle = args.oddstyle
else
rowstyle = args.evenstyle
end
local listText = args['list' .. listnum]
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)
: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
row
:tag('td')
:addClass('navbox-image')
:addClass(args.imageclass)
:css('width', '1px') -- Minimize width
:css('padding', '0px 0px 0px 2px')
:cssText(args.imagestyle)
:attr('rowspan', #listnums)
:tag('div')
:wikitext(processItem(args.image))
end
end
--
-- Tracking categories
--
local function needsHorizontalLists()
if border == 'subgroup' or args.tracking == 'no' then
return false
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
local function hasBackgroundColors()
for _, key in ipairs({'titlestyle', 'groupstyle', 'basestyle'}) do
if tostring(args[key]):find('background', 1, true) then
return true
end
end
end
local function isIllegible()
local styleratio = require('Module:Color contrast')._styleratio
for key, style in pairs(args) do
if tostring(key):match("style$") then
if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then
return true
end
end
end
return false
end
local function getTrackingCategories()
local cats = {}
if needsHorizontalLists() then table.insert(cats, 'Navigational boxes without horizontal lists') end
if hasBackgroundColors() then table.insert(cats, 'Navboxes using background colours') end
if isIllegible() then table.insert(cats, 'Potentially illegible navboxes') end
return cats
end
local function renderTrackingCategories(builder)
local title = mw.title.getCurrentTitle()
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
for _, cat in ipairs(getTrackingCategories()) do
builder:wikitext('[[Category:' .. cat .. ']]')
end
end
--
-- Main navbox tables
--
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
tbl
:addClass('collapsible')
:addClass(args.state or 'autocollapse')
end
tbl:css('border-spacing', 0)
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
tbl:cssText(args.innerstyle)
renderTitleRow(tbl)
renderAboveRow(tbl)
for i, listnum in ipairs(listnums) do
renderListRow(tbl, i, listnum)
end
renderBelowRow(tbl)
return tbl
end
function p._navbox(navboxArgs)
args = navboxArgs
for k, _ in pairs(args) do
if type(k) == 'string' then
local listnum = k:match('^list(%d+)$')
if listnum then table.insert(listnums, tonumber(listnum)) end
end
end
table.sort(listnums)
border = mw.text.trim(args.border or args[1] or '')
if border == 'child' then
border = 'subgroup'
end
-- render the main body of the navbox
local tbl = renderMainTable()
-- render the appropriate wrapper around the navbox, depending on the border param
local res = mw.html.create()
if border == 'none' then
local nav = res:tag('div')
:attr('role', 'navigation')
:node(tbl)
if args.title then
nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title))
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>')
:node(tbl)
:wikitext('<div>')
else
local nav = res:tag('div')
:attr('role', 'navigation')
:addClass('navbox')
:cssText(args.bodystyle)
:cssText(args.style)
:css('padding', '3px')
:node(tbl)
if args.title then
nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title))
else
nav:attr('aria-label', 'Navbox')
end
end
renderTrackingCategories(res)
return striped(tostring(res))
end
function p.navbox(frame)
if not getArgs then
getArgs = require('Module:Arguments').getArgs
end
args = getArgs(frame, {wrappers = 'Template:Navbox'})
-- Read the arguments in the order they'll be output in, to make references number in the right order.
local _
_ = args.title
_ = args.above
for i = 1, 20 do
_ = args["group" .. tostring(i)]
_ = args["list" .. tostring(i)]
end
_ = args.below
return p._navbox(args)
end
return p