--
-- This module implements {{Navbox}}
--
localp={}
localgetArgs-- lazily initialized
localargs
localborder
locallistnums
localODD_EVEN_MARKER='\127_ODDEVEN_\127'
localRESTART_MARKER='\127_ODDEVEN0_\127'
localREGEX_MARKER='\127_ODDEVEN(%d?)_\127'
locallists={
plainlist_t={
patterns={
'^plainlist$',
'%splainlist$',
'^plainlist%s',
'%splainlist%s'
},
found=false,
styles='Plainlist/styles.css'
},
hlist_t={
patterns={
'^hlist$',
'%shlist$',
'^hlist%s',
'%shlist%s'
},
found=false,
styles='Hlist/styles.css'
}
}
localfunctionhas_list_class(args_to_check)
for_,listinpairs(lists)do
ifnotlist.foundthen
for_,arginpairs(args_to_check)do
for_,patterninipairs(list.patterns)do
ifmw.ustring.find(argor'',pattern)then
list.found=true
break
end
end
iflist.foundthenbreakend
end
end
end
end
localfunctionstriped(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.
localorphanCat='[[Category:孤立的子导航框]]'
ifborder=='subgroup'andargs.orphan~='yes'then
-- No change; striping occurs in outermost navbox.
returnwikitext..orphanCat
end
localfirst,second='odd','even'
ifargs.evenoddthen
ifargs.evenodd=='swap'then
first,second=second,first
else
first=args.evenodd
second=first
end
end
localchanger
iffirst==secondthen
changer=first
else
localindex=0
changer=function(code)
ifcode=='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
returnfirst
end
index=index+1
returnindex%2==1andfirstorsecond
end
end
localregex=orphanCat:gsub('([%[%]])','%%%1')
return(wikitext:gsub(regex,''):gsub(REGEX_MARKER,changer))-- () omits gsub count
end
localfunctionaddNewline(s)
ifs:match('^[*:;#]')ors:match('^{|')then
return'\n'..s..'\n'
else
returns
end
end
localfunctionhas_collapsible_toggle()
returnargs.state~='off'
andargs.state~='plain'
end
localfunctionhas_navbar()
returnargs.navbar~='off'
andargs.navbar~='plain'
and(
args.name
or(
mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$','')
~='Template:Navbox'and
mw.getCurrentFrame():getParent():getTitle():gsub('/sandbox$','')
~='Template:Navbox subgroup'
)
)
end
-- extract text color from css, which is the only permitted inline CSS for the navbar
localfunctionextract_color(css_str)
-- return nil because navbar takes its argument into mw.html which handles
-- nil gracefully, removing the associated style attribute
returnmw.ustring.match(';'..css_str..';','.*;%s*([Cc][Oo][Ll][Oo][Rr]%s*:%s*.-)%s*;')ornil
end
localfunctionremoveilhSuffix(anchor)
localilh_suffix='(%S*:%-{.*}%-)'
returnmw.ustring.gsub(anchor,ilh_suffix,'')
end
localfunctionrenderNavBar(titleCell)
ifhas_navbar()then
localnavbar=require('Module:Navbar')._navbar
titleCell:wikitext(navbar{
args.name,
mini=1,
fontstyle=extract_color(
(args.basestyleor'')..';'..(args.titlestyleor'')
)
})
end
end
--
-- Title row
--
localfunctionrenderTitleRow(tbl)
ifnotargs.titlethenreturnend
has_list_class({args.titleclass})
localtitleRow=tbl:tag('tr')
localtitleCell=titleRow:tag('th'):attr('scope','col')
ifhas_collapsible_toggle()then
titleCell
:addClass('collapsible-title')
end
localtitleColspan=2
ifargs.imageleftthentitleColspan=titleColspan+1end
ifargs.imagethentitleColspan=titleColspan+1end
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',removeilhSuffix(mw.uri.anchorEncode(args.title))..args.argHash)
:addClass(args.titleclass)
:css('font-size','110%')
:css('margin','0 5em')
:wikitext(addNewline(args.title))
end
--
-- Above/Below rows
--
localfunctiongetAboveBelowColspan()
localret=2
ifargs.imageleftthenret=ret+1end
ifargs.imagethenret=ret+1end
returnret
end
localfunctionrenderAboveRow(tbl)
ifnotargs.abovethenreturnend
has_list_class({args.aboveclass})
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',(notargs.title)and(removeilhSuffix(mw.uri.anchorEncode(args.above))..args.argHash)ornil)
:wikitext(addNewline(args.above))
end
localfunctionrenderBelowRow(tbl)
ifnotargs.belowthenreturnend
has_list_class({args.belowclass})
tbl:tag('tr')
:tag('td')
:addClass('navbox-abovebelow')
:addClass(args.belowclass)
:cssText(args.basestyle)
:cssText(args.belowstyle)
:attr('colspan',getAboveBelowColspan())
:tag('div')
:wikitext(addNewline(args.below))
end
--
-- List rows
--
localfunctionrenderListRow(tbl,listnum)
localrow=tbl:tag('tr')
iflistnum==1andargs.imageleftthen
has_list_class({args.imageclass})
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(addNewline(args.imageleft))
end
ifargs['group'..listnum]then
has_list_class({args.groupclass})
localgroupCell=row:tag('th')
-- id for aria-labelledby attribute, if lone group with no title or above
iflistnum==1andnot(args.titleorargs.aboveorargs.group2)then
groupCell
:attr('id',removeilhSuffix(mw.uri.anchorEncode(args.group1))..args.argHash)
end
groupCell
:attr('scope','row')
:addClass('navbox-group')
:addClass(args.groupclass)
:cssText(args.basestyle)
:css('width',args.groupwidthor'1%')-- If groupwidth not specified, minimize width
groupCell
:cssText(args.groupstyle)
:cssText(args['group'..listnum..'style'])
:wikitext(args['group'..listnum])
end
has_list_class({args.listclass,args['list'..listnum..'class']})
locallistCell=row:tag('td')
ifargs['group'..listnum]then
listCell
:addClass('navbox-list-with-group')
else
listCell:attr('colspan',2)
end
ifnotargs.groupwidththen
listCell:css('width','100%')
end
localrowstyle-- usually nil so cssText(rowstyle) usually adds nothing
iflistnum%2==1then
rowstyle=args.oddstyle
else
rowstyle=args.evenstyle
end
locallistText=args['list'..listnum]
localoddEven=ODD_EVEN_MARKER
iflistText: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"')andRESTART_MARKERor'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',(listnum==1andargs.list1padding)orargs.listpaddingor'0em 0.25em')
:wikitext(addNewline(listText))
iflistnum==1andargs.imagethen
has_list_class({args.imageclass})
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(addNewline(args.image))
end
end
--
-- Tracking categories
--
localfunctionneedsHorizontalLists()
ifborder=='subgroup'orargs.tracking=='no'then
returnfalse
end
returnnotlists.hlist_t.foundandnotlists.plainlist_t.found
end
localfunctionhasBackgroundColors()
for_,keyinipairs({'titlestyle','groupstyle','basestyle','abovestyle','belowstyle'})do
iftostring(args[key]):find('background',1,true)then
returntrue
end
end
end
localfunctionargNameAndRealTitleAreDifferent()
ifborder=='subgroup'orargs.tracking=='no'then
returnfalse
end
ifhas_navbar()andargs.name~=mw.title.getCurrentTitle().textthen
returntrue
end
returnfalse
end
localfunctiongetTrackingCategories()
localcats={}
ifneedsHorizontalLists()thentable.insert(cats,'没有使用水平列表的导航框')end
ifhasBackgroundColors()thentable.insert(cats,'使用背景颜色的导航框')end
ifargNameAndRealTitleAreDifferent()thentable.insert(cats,'name參數和實際不同的導航框')end
returncats
end
localfunctionrenderTrackingCategories(builder)
localtitle=mw.title.getCurrentTitle()
iftitle.namespace~=10thenreturnend-- not in template space
localsubpage=title.subpageText
ifsubpage=='doc'orsubpage=='sandbox'orsubpage=='testcases'thenreturnend
for_,catinipairs(getTrackingCategories())do
builder:wikitext('[[Category:'..cat..']]')
end
end
--
-- Load the templatestyles for the navbox
--
localfunctionloadTemplateStyles(hiding_templatestyles)
localframe=mw.getCurrentFrame()
localhlist_templatestyles=''
iflists.hlist_t.foundthen
hlist_templatestyles=frame:extensionTag{
name='templatestyles',args={src=lists.hlist_t.styles}
}
end
-- a second workaround for [[phab:T303378]]
-- when that issue is fixed, we can actually use has_navbar not to emit the
-- tag here if we want
ifhas_navbar()andhlist_templatestyles==''then
hlist_templatestyles=frame:extensionTag{
name='templatestyles',args={src=lists.hlist_t.styles}
}
end
localplainlist_templatestyles=''
iflists.plainlist_t.foundthen
plainlist_templatestyles=frame:extensionTag{
name='templatestyles',args={src=lists.plainlist_t.styles}
}
end
localbase_templatestyles=frame:extensionTag{
name='templatestyles',args={src='Module:Navbox/styles.css'}
}
localtemplatestyles=''
ifargs['templatestyles']then
templatestyles=frame:extensionTag{
name='templatestyles',args={src=args['templatestyles']}
}
end
localchild_templatestyles=''
ifargs['child templatestyles']then
child_templatestyles=frame:extensionTag{
name='templatestyles',args={src=args['child templatestyles']}
}
end
returnmw.html.create('div')
:addClass('navbox-styles')
:wikitext(
-- hlist -> plainlist -> base is best-effort to preserve old Common.css ordering.
-- this ordering is not a guarantee because the rows of interest invoking
-- each class may not be on a specific page
hlist_templatestyles..
plainlist_templatestyles..
base_templatestyles..
templatestyles..
child_templatestyles..
table.concat(hiding_templatestyles)
)
:done()
end
-- work around [[phab:T303378]]
-- for each arg: find all the templatestyles strip markers, insert them into a
-- table. then remove all templatestyles markers from the arg
localfunctionmove_hiding_templatestyles(args)
localtemplatestyles_markers={}
localstrip_marker_pattern='(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)'
localargHash=0
fork,arginpairs(args)do
iftype(arg)=='string'then
formarkerinstring.gfind(arg,strip_marker_pattern)do
table.insert(templatestyles_markers,marker)
end
argHash=argHash+#arg
args[k]=string.gsub(arg,strip_marker_pattern,'')
end
end
ifnotargs.argHashthenargs.argHash=argHashend
returntemplatestyles_markers
end
--
-- Main navbox tables
--
localfunctionrenderMainTable()
localtbl=mw.html.create('table')
:addClass('nowraplinks')
:addClass(args.bodyclass)
has_list_class({args.bodyclass})
ifargs.titleandhas_collapsible_toggle()then
ifargs.state=='collapsed'thenargs.state='mw-collapsed'end
tbl
:addClass('mw-collapsible')
:addClass(args.stateor'autocollapse')
end
tbl:css('border-spacing',0)
ifborder=='subgroup'orborder=='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)
fori,listnuminipairs(listnums)do
renderListRow(tbl,listnum)
end
renderBelowRow(tbl)
returntbl
end
functionp._navbox(navboxArgs)
args=navboxArgs
localhiding_templatestyles=move_hiding_templatestyles(args)
listnums={}
fork,vinpairs(args)do
locallistnum=(''..k):match('^list(%d+)$')
iflistnumthentable.insert(listnums,tonumber(listnum))end
end
table.sort(listnums)
border=mw.text.trim(args.borderorargs[1]or'')
ifborder=='child'then
border='subgroup'
end
-- render the main body of the navbox
localtbl=renderMainTable()
-- render the appropriate wrapper around the navbox, depending on the border param
localres=mw.html.create()
ifborder=='none'then
res:node(loadTemplateStyles(hiding_templatestyles))
localnav=res:tag('div')
:attr('role','navigation')
:node(tbl)
-- aria-labelledby title, otherwise above, otherwise lone group
ifargs.titleorargs.aboveor(args.group1andnotargs.group2)then
nav:attr('aria-labelledby',removeilhSuffix(mw.uri.anchorEncode(args.titleorargs.aboveorargs.group1))..args.argHash)
else
nav:attr('aria-label','Navbox'..args.argHash)
end
elseifborder=='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>')-- mw.html 未支持 unclosed
:node(tbl)
:wikitext('<div>')-- mw.html 未支持 unclosed
else
res:node(loadTemplateStyles(hiding_templatestyles))
has_list_class({args.navboxclass})
localnav=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
ifargs.titleorargs.aboveor(args.group1andnotargs.group2)then
nav:attr('aria-labelledby',removeilhSuffix(mw.uri.anchorEncode(args.titleorargs.aboveorargs.group1))..args.argHash)
else
nav:attr('aria-label','Navbox'..args.argHash)
end
end
if(args.nocator'false'):lower()=='false'then
renderTrackingCategories(res)
end
returnstriped(tostring(res))
end
functionp.navbox(frame)
ifnotgetArgsthen
getArgs=require('Module:Arguments').getArgs
end
args=getArgs(frame,{wrappers={'Template:Navbox','Template:Navbox subgroup'}})
ifframe.args.borderthen
-- This allows Template:Navbox_subgroup to use {{#invoke:Navbox|navbox|border=...}}.
args.border=frame.args.border
end
-- Read the arguments in the order they'll be output in, to make references number in the right order.
local_
_=args.title
_=args.above
fori=1,35do
_=args["group"..tostring(i)]
_=args["list"..tostring(i)]
end
_=args.below
args.argHash=nil-- we shouldn't accept argHash passed from a template
returnp._navbox(args)
end
returnp