Module:Navbox: Difference between revisions

2,632 bytes removed ,  14 November 2023
Undo imported revision 14948 by user en>Uzume
m (1 revision imported)
(Undo imported revision 14948 by user en>Uzume)
Tag: Undo
 
(10 intermediate revisions by 4 users not shown)
Line 1: Line 1:
-- <nowiki>
--
--
-- This module implements {{Navbox}}
-- Implements {{navbox}}
--
--


local p = {}
local p = {}
local tnavbar = require( 'Module:Tnavbar' )
local yesno = require( 'Module:Yesno' )
local onmain = require('Module:Mainonly').on_main()
local page_title = mw.title.getCurrentTitle().fullText
--
-- Helper for inserting a new row into the navbox
--
-- @param tbl {mw.html table}
-- @return tbl {mw.html table}
--
local function insertRow( tbl )
return tbl:tag( 'tr' )
end


local navbar = require('Module:Navbar')._navbar
--
local getArgs -- lazily initialized
-- Creates the navbox table
--
-- @param args {table}
-- @return tbl {mw.html table}
--
local function createTbl( args )


local args
local tbl = mw.html.create( 'table' )
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)
tbl
-- Return wikitext with markers replaced for odd/even striping.
:addClass( yesno( args.subgroup ) and 'navbox-subgroup' or 'navbox' )
-- Child (subgroup) navboxes are flagged with a category that is removed
:addClass( 'nowraplinks' )
-- by parent navboxes. The result is that the category shows all pages
 
-- where a child navbox is not contained in a parent navbox.
if not yesno( args.subgroup ) and
local orphanCat = '[[Category:Navbox orphans]]'
( args.state == 'collapsed' or
if border == 'subgroup' and args.orphan ~= 'yes' then
  args.state == 'uncollapsed' or
-- No change; striping occurs in outermost navbox.
  args.state == 'autocollapse' or
return wikitext .. orphanCat
  -- defaults to autocollapse
end
  args.state == nil )
local first, second = 'odd', 'even'
then
if args.evenodd then
tbl:addClass( 'mw-collapsible' )
if args.evenodd == 'swap' then
 
first, second = second, first
if args.state == 'collapsed' then
else
tbl:addClass( 'mw-collapsed' )
first = args.evenodd
elseif args.state == 'uncollapsed' then
second = first
tbl:addClass('navbox-uncollapsed')
end
end
end
end
local changer
 
if first == second then
if yesno( args.collapsible ) then
changer = first
tbl:addClass( 'navbox-collapsible' )
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
end
local regex = orphanCat:gsub('([%[%]])', '%%%1')
return (wikitext:gsub(regex, ''):gsub(REGEX_MARKER, changer))  -- () omits gsub count
end


local function processItem(item, nowrapitems)
if args.style then
if item:sub(1, 2) == '{|' then
tbl:cssText( args.style )
-- 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
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)
-- manually set collapse/expand messages
-- bug causing the default database messages to be used
tbl
:attr( {
['cellspacing'] = '0',
['data-expandtext'] = 'show',
['data-collapsetext'] = 'hide',
['data-navbox-name'] = args.name
} )


if args.navbar ~= 'off' and args.navbar ~= 'plain' and not (not args.name and mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$', '') == 'Template:Navbox') then
return tbl
titleCell:wikitext(navbar{
end
args.name,
mini = 1,
fontstyle = (args.basestyle or '') .. ';' .. (args.titlestyle or '') .. ';background:none transparent;border:none;box-shadow:none;padding:0;'
})
end


--
-- Wrapper for [[Module:Tnavbar]]
--
-- @param args {table}
-- @return {string}
--
local function navbar( args )
return tnavbar._collapsible( { [1] = '<span style="display:none;">RS3 </span>'..args.title, [2] = args.name } )
end
end


--
--
--   Title row
-- Creates the header (what you see when the navbox is collapsed)
--
-- @param tbl {mw.html table}
-- @param args {table}
-- @return {mw.html table}
--
--
local function renderTitleRow(tbl)
local function header( tbl, args )
if not args.title then return end
local div = insertRow( tbl )
:tag( 'th' )
:attr( 'colspan', '2' )
:addClass( 'navbox-title' )
:attr( 'id' , 'navbox-title' )
:tag( 'div' )


local titleRow = tbl:tag('tr')
-- @todo move this to site css so we can simplify this (hook off a class)
 
-- to something like div:wikitext( args.name and navbar( args ) or args.title )
if args.titlegroup then
-- which can be appended to the above and returned straight away
titleRow
if args.name then
:tag('th')
div
:attr('scope', 'row')
:css( 'padding-right', args.state == 'plain' and '6em' or '0' )
:addClass('navbox-group')
:wikitext( navbar( args ) )
:addClass(args.titlegroupclass)
else
:cssText(args.basestyle)
div
:cssText(args.groupstyle)
:css( 'padding-left', args.state == 'plain' and '0' or '6em' )
:cssText(args.titlegroupstyle)
:wikitext( '<span style="display:none;">RS3 </span>'..args.title )
:wikitext(args.titlegroup)
end
end


local titleCell = titleRow:tag('th'):attr('scope', 'col')
return div:allDone()
 
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')
-- 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


--
--
--   Above/Below rows
-- Inserts a row into the navbox
--
-- @param tbl {mw.html table}
-- @param gtitle {string}
-- @param group {string}
-- @param gtype {string}
-- @param gcats {table}
-- @param style {string}
-- @return {mw.html table}
--
--
local function row( tbl, gtitle, group, gtype, gcats, style, _name, subgroup )
local tr = insertRow( tbl )
local td
if gtitle then
td = tr
:addClass( 'navbox-group' )
:tag( 'td' )
:addClass( 'navbox-group-title' )
:wikitext( gtitle )
:done()
:tag( 'td' )
else
td = tr
:addClass( 'navbox-group' )
:addClass( 'navbox-group-split' )
:tag( 'td' )
:addClass( 'navbox-group-title-hidden' )
:attr( 'colspan', '0' )
:css( 'display', 'none' )
:done()
:tag( 'td' )
:attr( 'colspan', '2' )
end


local function getAboveBelowColspan()
--[[
local ret = 2
  List styling
if args.imageleft then ret = ret + 1 end
  This is unlikely to be implemented in the near future due to it requiring extra css to work
if args.image then ret = ret + 1 end
  and mobile currently not supporting that css.
return ret
  As an example, it lets you do the following instead if using {{*}} all the time
end
  | group3 =
  * {{plink|foo}}
  * {{plink|bar}}
  * {{plink|baz}}
]]
if mw.ustring.match( group, '^%s*%*' ) then
td:newline()


local function renderAboveRow(tbl)
-- trim whitespace on bullets
if not args.above then return end
local spl = mw.text.split( group, '\n' )


tbl:tag('tr')
for i = 1, #spl do
:tag('td')
spl[i] = mw.text.trim( spl[i] )
:addClass('navbox-abovebelow')
end
: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 function renderBelowRow(tbl)
group = '\n' .. table.concat( spl, '\n' )
if not args.below then return end
end


tbl:tag('tr')
--local group2 = group
:tag('td')
--local group3 = group2
:addClass('navbox-abovebelow')
-- analytics
:addClass(args.belowclass)
:cssText(args.basestyle)
:cssText(args.belowstyle)
:attr('colspan', getAboveBelowColspan())
:tag('div')
:wikitext(processItem(args.below, args.nowrapitems))
end


--
--if _name then
--   List rows
-- local name = mw.ustring.gsub(_name,' ','_')
--
-- for v in mw.ustring.gmatch(group,'%[%[[^%]]+%]%]') do
local function renderListRow(tbl, index, listnum)
-- if mw.ustring.match(v,'%[%[File:.+|link=') then
local row = tbl:tag('tr')
-- local link = mw.ustring.match(v,'|link=([^%]|]+)')
-- if link then
-- local linkrep = mw.ustring.gsub(link,'([%%%]%[%-^$*()+?])','%%%1')
-- local _link = mw.ustring.gsub(link,' ','_')
-- local newfile = mw.ustring.gsub(v,'|link='..linkrep,string.format('|link=https://runescape.wiki/w/%s?f=%s',_link,name))
-- local w = mw.ustring.gsub(v,'([%%%]%[%-^$*()+?])','%%%1')
-- group2 = mw.ustring.gsub(group2,w,newfile)
-- end
-- elseif mw.ustring.match(v,'%[%[Category:') then
-- nothing
-- else
-- local link = mw.ustring.match(v,'%[%[([^%]|]+)')
-- local txt = mw.ustring.match(v,'%|([^%]|]+)') or link


if index == 1 and args.imageleft then
-- local newlink = ''
row
:tag('td')
:addClass('noviewer')
: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
-- black links if current page
local groupCell = row:tag('th')
-- if link == page_title then
-- newlink = string.format('<b>%s</b>',txt)
-- else
-- local _link = mw.ustring.gsub(link or '',' ','_')
-- newlink = string.format('[https://runescape.wiki.com/w/%s?n=%s %s]',_link,name,txt)
-- end
-- local w = mw.ustring.gsub(v,'([%%%]%[%-^$*()+?])','%%%1')
-- group2 = mw.ustring.gsub(group2,w,newlink)
-- end
-- end


-- id for aria-labelledby attribute, if lone group with no title or above
--[==[
if listnum == 1 and not (args.title or args.above or args.group2) then
fix [[these kind]]s of [[link]]s post analytics parse
groupCell
]==]
:attr('id', mw.uri.anchorEncode(args.group1))
-- group3 = group2
end


groupCell
-- for v in mw.ustring.gmatch(group2,'%[https://runescape.wiki.com/w[^%]]-%]%a') do
:attr('scope', 'row')
-- local rep = mw.ustring.gsub(v,'%]','')
:addClass('navbox-group')
-- rep = rep..']'
:addClass(args.groupclass)
-- local w = mw.ustring.gsub(v,'([%%%]%[%-^$*()+?])','%%%1')
:cssText(args.basestyle)
:css('width', args.groupwidth or '1%') -- If groupwidth not specified, minimize width


groupCell
-- group3 = mw.ustring.gsub(group2,w,rep)
:cssText(args.groupstyle)
-- end
:cssText(args['group' .. listnum .. 'style'])
--end
:wikitext(args['group' .. listnum])
end


local listCell = row:tag('td')
td
:addClass( 'navbox-list' )
:wikitext( group ) --group3


if args['group' .. listnum] then
if gtype and mw.ustring.lower( gtype ) == 'subgroup' then
listCell
td
:css('text-align', 'left')
:addClass( 'navbox-parent' )
:css('border-left-width', '2px')
:css( {
:css('border-left-style', 'solid')
padding = '0',
else
['border-bottom'] = '0'
listCell:attr('colspan', 2)
} )
end
end


if not args.groupwidth then
if style then
listCell:css('width', '100%')
td:cssText( style )
end
end
 
local rowstyle  -- usually nil so cssText(rowstyle) usually adds nothing
-- add subgroup categories
if index % 2 == 1 then
if next(gcats) and onmain then
rowstyle = args.oddstyle
first_char = page_title:sub(1,1)
else
title_pattern = "[" .. first_char:upper() .. first_char:lower() .. "]" .. page_title:sub(2)
rowstyle = args.evenstyle
title_pattern = title_pattern :gsub("%(","%%(")
end
:gsub("%)","%%)")
 
:gsub("_"," ")
local listText = args['list' .. listnum]
:gsub(" ","[_ ]")
local oddEven = ODD_EVEN_MARKER
:gsub("%-","%%-")
if listText:sub(1, 12) == '</div><table' then
local link_patterns = {
-- Assume list text is for a subgroup navbox so no automatic striping for this row.
"%[%["..title_pattern.."%]%]", "%[%["..title_pattern.."%|", "{{[Pp]link%|"..title_pattern.."}}",
oddEven = listText:find('<th[^>]*"navbox%-title"') and RESTART_MARKER or 'odd'
"{{[Pp]link%|"..title_pattern.."%|", "{{[Pp]linkp%|"..title_pattern.."}}",
end
"{{[Pp]linkp%|"..title_pattern.."%|", "{{[Ii]linkp%|"..title_pattern.."}}",
listCell
"{{[Ii]linkp%|"..title_pattern.."%|", "{{[Cc]hatl%|"..title_pattern.."}}",
:css('padding', '0px')
"{{[Cc]hatl%|"..title_pattern.."%|"
:cssText(args.liststyle)
}
:cssText(rowstyle)
for _,v in ipairs(link_patterns) do
:cssText(args['list' .. listnum .. 'style'])
if group:match(v) then
:addClass('navbox-list')
for _,cat in ipairs(gcats) do
:addClass('navbox-' .. oddEven)
td:wikitext('[[Category:'..cat..']]')
:addClass(args.listclass)
end
:addClass(args['list' .. listnum .. 'class'])
break
:tag('div')
end
:css('padding', (index == 1 and args.list1padding) or args.listpadding or '0em 0.25em')
end
:wikitext(processItem(listText, args.nowrapitems))
 
if index == 1 and args.image then
row
:tag('td')
:addClass('noviewer')
: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
return td:allDone()
end
end


--
--
--   Tracking categories
-- Inserts a footer into the navbox
--
-- @param tbl {mw.html table}
-- @param args {table}
-- @return {mw.html table}
--
--
local function footer( tbl, args )
local th = insertRow( tbl )
:tag( 'th' )
:attr( 'colspan', '2' )
:addClass( 'navbox-footer' )


local function needsHorizontalLists()
if args.fstyle then
if border == 'subgroup' or args.tracking == 'no' then
th:cssText( args.fstyle )
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


local function hasBackgroundColors()
if mw.ustring.match( args.footer, '^%s*%*' ) then
for _, key in ipairs({'titlestyle', 'groupstyle', 'basestyle', 'abovestyle', 'belowstyle'}) do
th:newline()
if tostring(args[key]):find('background', 1, true) then
 
return true
-- trim whitespace on bullets
end
local spl = mw.text.split( args.footer, '\n' )
end
end


local function hasBorders()
for i = 1, #spl do
for _, key in ipairs({'groupstyle', 'basestyle', 'abovestyle', 'belowstyle'}) do
spl[i] = mw.text.trim( spl[i] )
if tostring(args[key]):find('border', 1, true) then
return true
end
end
end
end


local function isIllegible()
args.footer = table.concat( spl, '\n' )
local styleratio = require('Module:Color contrast')._styleratio


for key, style in pairs(args) do
th:addClass( 'navbox-list' )
if tostring(key):match("style$") then
if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then
return true
end
end
end
end
return false
end


local function getTrackingCategories()
th:wikitext( args.footer )
local cats = {}
 
if needsHorizontalLists() then table.insert(cats, 'Navigational boxes without horizontal lists') end
return th:allDone()
if hasBackgroundColors() then table.insert(cats, 'Navboxes using background colours') end
if isIllegible() then table.insert(cats, 'Potentially illegible navboxes') end
if hasBorders() then table.insert(cats, 'Navboxes using borders') end
return cats
end
end


local function renderTrackingCategories(builder)
--
-- Adds [[Category:Navigational templates]] to navbox template pages
--
-- @return {string}
--
local function categories()
local title = mw.title.getCurrentTitle()
local title = mw.title.getCurrentTitle()
if title.namespace ~= 10 then return end -- not in template space
local page = title.text
local subpage = title.subpageText
local ns = title.nsText
if subpage == 'doc' or subpage == 'sandbox' or subpage == 'testcases' then return end


for _, cat in ipairs(getTrackingCategories()) do
if ns == 'Template' then
builder:wikitext('[[Category:' .. cat .. ']]')
-- sort in category by pagename
return '[[Category:Navigational templates|' .. page .. ']]'
else
return ''
end
end
end
end


--
--
--   Main navbox tables
-- Adds [[Template:Navbox/doc]] to navbox template pages
--
--
local function renderMainTable()
-- @param args {table}
local tbl = mw.html.create('table')
-- @return {string}
:addClass('nowraplinks')
--
:addClass(args.bodyclass)
local function docs( args )
local frame = mw.getCurrentFrame()
local title = mw.title.getCurrentTitle()
local base = title.baseText
local ns = title.nsText


if args.title and (args.state ~= 'plain' and args.state ~= 'off') then
-- not if a subpage of [[Template:Navbox]]
if args.state == 'collapsed' then args.state = 'mw-collapsed' end
if base ~= 'Navbox' and
tbl
-- in template ns
:addClass('mw-collapsible')
ns == 'Template' and
:addClass(args.state or 'autocollapse')
-- not a navbox group within a navbox
end
not yesno( args.subgroup ) and
 
-- not a collapsible navbox within a navbox
tbl:css('border-spacing', 0)
not yesno( args.collapsible ) and
if border == 'subgroup' or border == 'none' then
-- not if the doc argument is not set to "yes"
tbl
yesno( args.doc, false )
:addClass('navbox-subgroup')
then
:cssText(args.bodystyle)
return frame:expandTemplate{ title = 'Navbox/doc' }
:cssText(args.style)
else
else  -- regular navbox - bodystyle and style will be applied to the wrapper table
return ''
tbl
:addClass('navbox-inner')
:css('background', 'transparent')
:css('color', 'inherit')
end
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
end


function p._navbox(navboxArgs)
--
args = navboxArgs
-- Navbox method to allow it to be called by other modules
listnums = {}
--
 
-- @param _args {table}
for k, _ in pairs(args) do
-- @return {string}
if type(k) == 'string' then
--
local listnum = k:match('^list(%d+)$')
function p._navbox( _args )
if listnum then table.insert(listnums, tonumber(listnum)) end
local args = {}
local wkCss = ''
local wkDiv = ''
local j
-- preserves parser function behaviour where an empty string is considered undefined
-- or nil in lua's case
for k, v in pairs( _args ) do
if v ~= '' then
args[k] = v
end
end
end
end
table.sort(listnums)


border = mw.text.trim(args.border or args[1] or '')
local tbl = createTbl( args )
if border == 'child' then
 
border = 'subgroup'
if not yesno( args.subgroup ) then
tbl = header( tbl, args )
end
end


-- render the main body of the navbox
-- insert up to 20 rows
local tbl = renderMainTable()
--
-- 20 is a limit inherited from wikipedia when we copied this over
-- and we've never had a reason to extend it
for i = 1, 20 do
j = tostring( i )


-- render the appropriate wrapper around the navbox, depending on the border param
if args['group' .. j] then
local res = mw.html.create()
local gcats = {}
if border == 'none' then
for p = 1, 20 do
local nav = res:tag('div')
local q = tostring ( p )
:attr('role', 'navigation')
:node(tbl)
if args['g' .. j .. 'cat' .. q] then
-- aria-labelledby title, otherwise above, otherwise lone group
table.insert(gcats, args['g' .. j .. 'cat' .. q])
if args.title or args.above or (args.group1 and not args.group2) then
else
nav:attr('aria-labelledby', mw.uri.anchorEncode(args.title or args.above or args.group1))
break
else
end
nav:attr('aria-label', 'Navbox')
end
end
elseif border == 'subgroup' then
tbl = row( tbl, args['gtitle' .. j], args['group' .. j], args['gtype' .. j], gcats, args['style' .. j], args.name, args.subgroup )
-- 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')
:addClass(args.navboxclass)
:cssText(args.bodystyle)
:cssText(args.style)
:css('padding', '3px')
: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
else
nav:attr('aria-label', 'Navbox')
break
end
end
end
end


if (args.nocat or 'false'):lower() == 'false' then
if args.footer then
renderTrackingCategories(res)
tbl = footer( tbl, args )
end
end
return striped(tostring(res))
end


function p.navbox(frame)
tbl = tostring( tbl )
if not getArgs then
 
getArgs = require('Module:Arguments').getArgs
local cats = ''
if not yesno(args.subgroup) and not yesno(args.hidecat) then
cats = categories()
end
end
args = getArgs(frame, {wrappers = {'Template:Navbox'}})
local docs = docs( args )


-- Read the arguments in the order they'll be output in, to make references number in the right order.
return tbl .. cats .. docs
local _
end
_ = args.title
_ = args.above
for i = 1, 20 do
_ = args["group" .. tostring(i)]
_ = args["list" .. tostring(i)]
end
_ = args.below


return p._navbox(args)
--
-- Main navbox method accessed through #invoke
--
-- @param frame {table}
-- @return {string}
--
function p.navbox( frame )
local args = frame:getParent().args
return p._navbox( args )
end
end


return p
return p
-- </nowiki>