Module:Sidebar: Difference between revisions

From Random Island Wiki
Jump to navigation Jump to search
>Toohool
(newline fix)
>Izno
(make wraplinks work like it used to)
 
(30 intermediate revisions by 9 users not shown)
Line 2: Line 2:
-- This module implements {{Sidebar}}
-- This module implements {{Sidebar}}
--
--
require('Module:No globals')
local cfg = mw.loadData('Module:Sidebar/configuration')
 
local p = {}
local p = {}
local HtmlBuilder = require('Module:HtmlBuilder')
local Navbar = require('Module:Navbar')
local function _sidebar(args)
    local root = HtmlBuilder.create('table')
   
    root
        .addClass('vertical-navbox')
        .addClass(args.wraplinks ~= 'true' and 'nowraplinks')
        .addClass(args.bodyclass or args.class)
        .attr('cellspacing', args.cellspacing or 5)
        .attr('cellpadding', args.cellpadding or 0)
        .css('float', args.float or 'right')
        .css('clear', (args.float == 'none' and 'both') or args.float or 'right')
        .css('width', args.width or '22.0em')
        .css('margin', args.float == 'left' and '0 1.0em 1.0em 0' or '0 0 1.0em 1.0em')
        .css('background', '#f9f9f9')
        .css('border', '1px solid #aaa')
        .css('padding', '0.2em')
        .css('border-spacing', '0.4em 0')
        .css('text-align', 'center')
        .css('line-height', '1.4em')
        .css('font-size', '88%')
        .cssText(args.bodystyle or args.style)


    if args.outertitle then
local getArgs = require('Module:Arguments').getArgs
        root
            .tag('caption')
                .addClass(args.outertitleclass)
                .css('padding-bottom', '0.2em')
                .css('font-size', '125%')
                .css('line-height', '1.2em')
                .css('font-weight', 'bold')
                .cssText(args.outertitlestyle)
                .wikitext(args.outertitle)
    end


    if args.topimage then
--[[
        local imageCell = root.tag('tr').tag('td')
Categorizes calling templates and modules with a 'style' parameter of any sort
       
for tracking to convert to TemplateStyles.
        imageCell
            .addClass(args.topimageclass)
            .css('padding', '0.4em 0')
            .cssText(args.topimagestyle)
            .wikitext(args.topimage)
       
        if args.topcaption then
            imageCell
                .tag('div')
                    .css('padding-top', '0.2em')
                    .css('line-height', '1.2em')
                    .cssText(args.topcaptionstyle)
                    .wikitext(args.topcaption)
        end
    end
   
    if args.pretitle then
        root
            .tag('tr')
                .tag('td')
                    .addClass(args.pretitleclass)
                    .cssText(args.basestyle)
                    .css('padding-top', args.topimage and '0.2em' or '0.4em')
                    .css('line-height', '1.2em')
                    .cssText(args.pretitlestyle)
                    .wikitext(args.pretitle)
    end


    if args.title then
TODO after a long cleanup: Catch sidebars in other namespaces than Template and Module.
        root
TODO would probably want to remove /log and /archive as CS1 does
            .tag('tr')
]]
                .tag('th')
local function categorizeTemplatesWithInlineStyles(args)
                    .addClass(args.titleclass)
local title = mw.title.getCurrentTitle()
                    .cssText(args.basestyle)
if title.namespace ~= 10 and title.namespace ~= 828 then return '' end
                    .css('padding', '0.2em 0.4em 0.2em')
for _, pattern in ipairs (cfg.i18n.pattern.uncategorized_conversion_titles) do
                    .css('padding-top', args.pretitle and 0)
if title.text:match(pattern) then return '' end
                    .css('font-size', '145%')
end
                    .css('line-height', '1.2em')
                    .cssText(args.titlestyle)
for key, _ in pairs(args) do
                    .wikitext(args.title)
if mw.ustring.find(key, cfg.i18n.pattern.style_conversion) or key == 'width' then
    end
return cfg.i18n.category.conversion
end
end
end


    if args.image then
--[[
        local imageCell = root.tag('tr').tag('td')
For compatibility with the original {{sidebar with collapsible lists}}
       
implementation, which passed some parameters through {{#if}} to trim their
        imageCell
whitespace. This also triggered the automatic newline behavior.
            .addClass(args.imageclass)
]]
            .css('padding', '0.2em 0 0.4em')
-- See ([[meta:Help:Newlines and spaces#Automatic newline]])
            .cssText(args.imagestyle)
local function trimAndAddAutomaticNewline(s)
            .wikitext(args.image)
s = mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1")
           
if mw.ustring.find(s, '^[#*:;]') or mw.ustring.find(s, '^{|') then
        if args.caption then
return '\n' .. s
            imageCell
else
                .tag('div')
return s
                    .css('padding-top', '0.2em')
end
                    .css('line-height', '1.2em')
end
                    .cssText(args.captionstyle)
                    .wikitext(args.caption)
        end
    end
   
    if args.above then
        root
            .tag('tr')
                .tag('td')
                    .addClass(args.aboveclass)
                    .css('padding', '0.3em 0.4em 0.3em')
                    .css('font-weight', 'bold')
                    .cssText(args.abovestyle)
                    .newline()      -- newline required for bullet-points to work
                    .wikitext(args.above)
    end


    local rowNums = {}
--[[
    for k, v in pairs(args) do
Finds whether a sidebar has a subgroup sidebar.
        k = '' .. k
]]
        local num = k:match('^heading(%d+)$') or k:match('^content(%d+)$')
local function hasSubgroup(s)
        if num then table.insert(rowNums, tonumber(num)) end
if mw.ustring.find(s, cfg.i18n.pattern.subgroup) then
    end
return true
    table.sort(rowNums)
else
    -- remove duplicates from the list (e.g. 3 will be duplicated if both heading3 and content3 are specified)
return false
    for i = #rowNums, 1, -1 do
end
        if rowNums[i] == rowNums[i - 1] then
end
            table.remove(rowNums, i)
        end
    end


    for i, num in ipairs(rowNums) do
--[[
        local heading = args['heading' .. num]
Main sidebar function. Takes the frame, args, and an optional collapsibleClass.
        if heading then
The collapsibleClass is and should be used only for sidebars with collapsible
            root
lists, as in p.collapsible.
                .tag('tr')
]]
                    .tag('th')
function p.sidebar(frame, args, collapsibleClass)
                        .addClass(args.headingclass)
if not args then
                        .css('padding', '0.1em')
args = getArgs(frame)
                        .cssText(args.basestyle)
end
                        .cssText(args.headingstyle)
local root = mw.html.create()
                        .cssText(args['heading' .. num .. 'style'])
local child = args.child and mw.text.trim(args.child) == cfg.i18n.child_yes
                        .wikitext(heading)
        end
       
        local content = args['content' .. num]
        if content then
            root
                .tag('tr')
                    .tag('td')
                        .addClass(args.contentclass)
                        .css('padding', '0 0.1em 0.4em')
                        .cssText(args.contentstyle)
                        .cssText(args['content' .. num .. 'style'])
                        .newline()
                        .wikitext(content)
                        .done()
                    .newline()  -- Without a linebreak after the </td>, a nested list like "* {{hlist| ...}}" doesn't parse correctly.
        end
    end


    if args.below then
root = root:tag('table')
        root
if not child then
            .tag('tr')
root  
                .tag('td')
:addClass(cfg.i18n.class.sidebar)
                    .addClass(args.belowclass)
-- force collapsibleclass to be sidebar-collapse otherwise output nothing
                    .css('padding', '0.3em 0.4em 0.3em')
:addClass(collapsibleClass == cfg.i18n.class.collapse and cfg.i18n.class.collapse or nil)
                    .css('font-weight', 'bold')
:addClass('nomobile')
                    .cssText(args.belowstyle)
:addClass(args.float == cfg.i18n.float_none and cfg.i18n.class.float_none or nil)
                    .newline()
:addClass(args.float == cfg.i18n.float_left and cfg.i18n.class.float_left or nil)
                    .wikitext(args.below)
:addClass(args.wraplinks ~= cfg.i18n.wrap_true and cfg.i18n.class.wraplinks or nil)
    end
:addClass(args.bodyclass or args.class)
:css('width', args.width or nil)
:cssText(args.bodystyle or args.style)


    local navbarArg = args.navbar or args.tnavbar
if args.outertitle then
    if navbarArg ~= 'none' and navbarArg ~= 'off' then
root
        root
:tag('caption')
            .tag('tr')
:addClass(cfg.i18n.class.outer_title)
                .tag('td')
:addClass(args.outertitleclass)
                    .css('text-align', 'right')
:cssText(args.outertitlestyle)
                    .css('font-size', '115%')
:wikitext(args.outertitle)
                    .cssText(args.navbarstyle or args.tnavbarstyle)
end
                    .wikitext(Navbar.navbar({
                        args.name or mw.title.getCurrentTitle().fullText,
                        mini = 1,
                        fontstyle = args.navbarfontstyle or args.tnavbarfontstyle
                    }))
    end  


    return tostring(root)
if args.topimage then
local imageCell = root:tag('tr'):tag('td')
 
imageCell
:addClass(cfg.i18n.class.top_image)
:addClass(args.topimageclass)
:cssText(args.topimagestyle)
:wikitext(args.topimage)
 
if args.topcaption then
imageCell
:tag('div')
:addClass(cfg.i18n.class.top_caption)
:cssText(args.topcaptionstyle)
:wikitext(args.topcaption)
end
end
 
if args.pretitle then
root
:tag('tr')
:tag('td')
:addClass(args.topimage and cfg.i18n.class.pretitle_with_top_image
or cfg.i18n.class.pretitle)
:addClass(args.pretitleclass)
:cssText(args.basestyle)
:cssText(args.pretitlestyle)
:wikitext(args.pretitle)
end
else
root
:addClass(cfg.i18n.class.subgroup)
:addClass(args.bodyclass or args.class)
:cssText(args.bodystyle or args.style)
end
 
if args.title then
if child then
root
:wikitext(args.title)
else
root
:tag('tr')
:tag('th')
:addClass(args.pretitle and cfg.i18n.class.title_with_pretitle
or cfg.i18n.class.title)
:addClass(args.titleclass)
:cssText(args.basestyle)
:cssText(args.titlestyle)
:wikitext(args.title)
end
end
 
if args.image then
local imageCell = root:tag('tr'):tag('td')
 
imageCell
:addClass(cfg.i18n.class.image)
:addClass(args.imageclass)
:cssText(args.imagestyle)
:wikitext(args.image)
 
if args.caption then
imageCell
:tag('div')
:addClass(cfg.i18n.class.caption)
:cssText(args.captionstyle)
:wikitext(args.caption)
end
end
 
if args.above then
root
:tag('tr')
:tag('td')
:addClass(cfg.i18n.class.above)
:addClass(args.aboveclass)
:cssText(args.abovestyle)
:newline() -- newline required for bullet-points to work
:wikitext(args.above)
end
 
local rowNums = {}
for k, v in pairs(args) do
k = '' .. k
local num = k:match('^heading(%d+)$') or k:match('^content(%d+)$')
if num then table.insert(rowNums, tonumber(num)) end
end
table.sort(rowNums)
-- remove duplicates from the list (e.g. 3 will be duplicated if both heading3
-- and content3 are specified)
for i = #rowNums, 1, -1 do
if rowNums[i] == rowNums[i - 1] then
table.remove(rowNums, i)
end
end
 
for i, num in ipairs(rowNums) do
local heading = args['heading' .. num]
if heading then
root
:tag('tr')
:tag('th')
:addClass(cfg.i18n.class.heading)
:addClass(args.headingclass)
:addClass(args['heading' .. num .. 'class'])
:cssText(args.basestyle)
:cssText(args.headingstyle)
:cssText(args['heading' .. num .. 'style'])
:newline()
:wikitext(heading)
end
 
local content = args['content' .. num]
if content then
root
:tag('tr')
:tag('td')
:addClass(hasSubgroup(content) and cfg.i18n.class.content_with_subgroup
or cfg.i18n.class.content)
:addClass(args.contentclass)
:addClass(args['content' .. num .. 'class'])
:cssText(args.contentstyle)
:cssText(args['content' .. num .. 'style'])
:newline()
:wikitext(content)
:done()
-- Without a linebreak after the </td>, a nested list like
-- "* {{hlist| ...}}" doesn't parse correctly.
:newline()
end
end
 
if args.below then
root
:tag('tr')
:tag('td')
:addClass(cfg.i18n.class.below)
:addClass(args.belowclass)
:cssText(args.belowstyle)
:newline()
:wikitext(args.below)
end
 
if not child then
if args.navbar ~= cfg.i18n.navbar_none and args.navbar ~= cfg.i18n.navbar_off and
(args.name or frame:getParent():getTitle():gsub(cfg.i18n.pattern.sandbox, '') ~=
cfg.i18n.title_not_to_add_navbar) then
root
:tag('tr')
:tag('td')
:addClass(cfg.i18n.class.navbar)
:cssText(args.navbarstyle)
:wikitext(require('Module:Navbar')._navbar{
args.name,
mini = 1,
fontstyle = args.navbarfontstyle
})
end
end
local base_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = cfg.i18n.templatestyles }
}
local templatestyles = ''
if args['templatestyles'] and args['templatestyles'] ~= '' then
templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['templatestyles'] }
}
end
local child_templatestyles = ''
if args['child templatestyles'] and args['child templatestyles'] ~= '' then
child_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['child templatestyles'] }
}
end
local grandchild_templatestyles = ''
if args['grandchild templatestyles'] and args['grandchild templatestyles'] ~= '' then
grandchild_templatestyles = frame:extensionTag{
name = 'templatestyles', args = { src = args['grandchild templatestyles'] }
}
end
 
return table.concat({
base_templatestyles,
templatestyles,
child_templatestyles,
grandchild_templatestyles,
tostring(root),
(child and cfg.i18n.category.child or ''),
categorizeTemplatesWithInlineStyles(args)
})
end
end


function _collapsibleSidebar(args)
local function list_title(args, is_centered_list_titles, num)
    args.abovestyle = 'border-top: 1px solid #aaa; border-bottom: 1px solid #aaa;' .. (args.abovestyle or '')
    args.belowstyle = 'border-top: 1px solid #aaa; border-bottom: 1px solid #aaa;' .. (args.belowstyle or '')
local title_text = trimAndAddAutomaticNewline(args['list' .. num .. 'title']
    args.navbarstyle = 'padding-top: 0.6em;' .. (args.navbarstyle or args.tnavbarstyle or '')
or cfg.i18n.default_list_title)
   
 
    for k, v in pairs(args) do
local title
        local num = ('' .. k):match('^list(%d+)$')
if is_centered_list_titles then
        if num then  
-- collapsible can be finicky, so provide some CSS/HTML to support
            local expand = args.expanded and (args.expanded == 'all' or args.expanded == args['list' .. num .. 'name'])
title = mw.html.create('div')
           
:addClass(cfg.i18n.class.list_title_centered)
            local row = HtmlBuilder.create('div')
:wikitext(title_text)
            row
else
                .addClass('NavFrame')
title = mw.html.create()
                .addClass((not expand) and 'collapsed')
:wikitext(title_text)
                .css('border', 'none')
end
                .css('padding', 0)
                .cssText(args.listframestyle)
local title_container = mw.html.create('div')
                .cssText(args['list' .. num .. 'style'])
:addClass(cfg.i18n.class.list_title)
                .tag('div')
-- don't /need/ a listnumtitleclass because you can do
                    .addClass('NavHead')
-- .templateclass .listnumclass .sidebar-list-title
                    .addClass(args.listtitleclass)
:addClass(args.listtitleclass)
                    .css('font-size', '105%')
:cssText(args.basestyle)
                    .css('background', 'transparent')
:cssText(args.listtitlestyle)
                    .css('text-align', 'left')
:cssText(args['list' .. num .. 'titlestyle'])
                    .cssText(args.basestyle)
:node(title)
                    .cssText(args.listtitlestyle)
:done()
                    .cssText(args['list' .. num .. 'titlestyle'])
                    .wikitext(args['list' .. num .. 'title'] or 'List')
return title_container
                    .done()
                .tag('div')
                    .addClass('NavContent')
                    .addClass(args.listclass)
                    .addClass(args['list' .. num .. 'class'])
                    .css('font-size', '105%')
                    .css('padding', '0.2em 0 0.4em')
                    .css('text-align', 'center')
                    .cssText(args.liststyle)
                    .cssText(args['list' .. num .. 'style'])
                    .newline()
                    .wikitext(args['list' .. num])
                   
            args['content' .. num] = tostring(row)
        end
    end
   
    return _sidebar(args)
end
end
                     
 
function makeWrapper(func)
--[[
    return function(frame)
Main entry point for sidebar with collapsible lists.
        local origArgs
Does the work of creating the collapsible lists themselves and including them
        if frame == mw.getCurrentFrame() then
into the args.
            -- We're being called via #invoke. If the invoking template passed any args, use
]]
            -- them. Otherwise, use the args that were passed into the template.
function p.collapsible(frame)
            origArgs = frame:getParent().args
local args = getArgs(frame)
            for k, v in pairs(frame.args) do
if not args.name and
                origArgs = frame.args
frame:getParent():getTitle():gsub(cfg.i18n.pattern.collapse_sandbox, '') ==
                break
cfg.i18n.collapse_title_not_to_add_navbar then
            end
args.navbar = cfg.i18n.navbar_none
        else
end
            -- We're being called from another module or from the debug console, so assume
 
            -- the args are passed in directly.
local contentArgs = {}
            origArgs = frame
        end
local is_centered_list_titles
   
if args['centered list titles'] and args['centered list titles'] ~= '' then
        -- ParserFunctions considers the empty string to be false, so to preserve the previous
is_centered_list_titles = true
        -- behavior of the template, change any empty arguments to nil, so Lua will consider
else
        -- them false too.
is_centered_list_titles = false
        local args = {}
end
        for k, v in pairs(origArgs) do
 
            if v ~= '' then
for k, v in pairs(args) do
                args[k] = v
local num = string.match(k, '^list(%d+)$')
            end
if num then
        end
local expand = args.expanded and
   
(args.expanded == 'all' or args.expanded == args['list' .. num .. 'name'])
        return func(args)
local row = mw.html.create('div')
    end
row
:addClass(cfg.i18n.class.list)
:addClass('mw-collapsible')
:addClass((not expand) and 'mw-collapsed' or nil)
:addClass(args['list' .. num .. 'class'])
:cssText(args.listframestyle)
:cssText(args['list' .. num .. 'framestyle'])
:node(list_title(args, is_centered_list_titles, num))
:tag('div')
:addClass(cfg.i18n.class.list_content)
:addClass('mw-collapsible-content')
-- don't /need/ a listnumstyleclass because you can do
-- .templatename .listnumclass .sidebar-list
:addClass(args.listclass)
:cssText(args.liststyle)
:cssText(args['list' .. num .. 'style'])
:wikitext(trimAndAddAutomaticNewline(args['list' .. num]))
 
contentArgs['content' .. num] = tostring(row)
end
end
 
for k, v in pairs(contentArgs) do
args[k] = v
end
 
return p.sidebar(frame, args, cfg.i18n.class.collapse)
end
end


return {
return p
    sidebar = makeWrapper(_sidebar),
    collapsible = makeWrapper(_collapsibleSidebar)
}

Latest revision as of 15:35, 2 August 2021

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

--
-- This module implements {{Sidebar}}
--
require('Module:No globals')
local cfg = mw.loadData('Module:Sidebar/configuration')

local p = {}

local getArgs = require('Module:Arguments').getArgs

--[[
Categorizes calling templates and modules with a 'style' parameter of any sort
for tracking to convert to TemplateStyles.

TODO after a long cleanup: Catch sidebars in other namespaces than Template and Module.
TODO would probably want to remove /log and /archive as CS1 does
]]
local function categorizeTemplatesWithInlineStyles(args)
	local title = mw.title.getCurrentTitle()
	if title.namespace ~= 10 and title.namespace ~= 828 then return '' end
	for _, pattern in ipairs (cfg.i18n.pattern.uncategorized_conversion_titles) do
		if title.text:match(pattern) then return '' end
	end
	
	for key, _ in pairs(args) do
		if mw.ustring.find(key, cfg.i18n.pattern.style_conversion) or key == 'width' then
			return cfg.i18n.category.conversion
		end
	end
end

--[[
For compatibility with the original {{sidebar with collapsible lists}}
implementation, which passed some parameters through {{#if}} to trim their
whitespace. This also triggered the automatic newline behavior.
]]
-- See ([[meta:Help:Newlines and spaces#Automatic newline]])
local function trimAndAddAutomaticNewline(s)
	s = mw.ustring.gsub(s, "^%s*(.-)%s*$", "%1")
	if mw.ustring.find(s, '^[#*:;]') or mw.ustring.find(s, '^{|') then
		return '\n' .. s
	else
		return s
	end
end

--[[
Finds whether a sidebar has a subgroup sidebar.
]]
local function hasSubgroup(s)
	if mw.ustring.find(s, cfg.i18n.pattern.subgroup) then
		return true
	else
		return false
	end
end

--[[
Main sidebar function. Takes the frame, args, and an optional collapsibleClass.
The collapsibleClass is and should be used only for sidebars with collapsible
lists, as in p.collapsible.
]]
function p.sidebar(frame, args, collapsibleClass)
	if not args then
		args = getArgs(frame)
	end
	local root = mw.html.create()
	local child = args.child and mw.text.trim(args.child) == cfg.i18n.child_yes

	root = root:tag('table')
	if not child then
		root 
			:addClass(cfg.i18n.class.sidebar)
			-- force collapsibleclass to be sidebar-collapse otherwise output nothing
			:addClass(collapsibleClass == cfg.i18n.class.collapse and cfg.i18n.class.collapse or nil)
			:addClass('nomobile')
			:addClass(args.float == cfg.i18n.float_none and cfg.i18n.class.float_none or nil)
			:addClass(args.float == cfg.i18n.float_left and cfg.i18n.class.float_left or nil)
			:addClass(args.wraplinks ~= cfg.i18n.wrap_true and cfg.i18n.class.wraplinks or nil)
			:addClass(args.bodyclass or args.class)
			:css('width', args.width or nil)
			:cssText(args.bodystyle or args.style)

		if args.outertitle then
			root
				:tag('caption')
					:addClass(cfg.i18n.class.outer_title)
					:addClass(args.outertitleclass)
					:cssText(args.outertitlestyle)
					:wikitext(args.outertitle)
		end

		if args.topimage then
			local imageCell = root:tag('tr'):tag('td')

			imageCell
				:addClass(cfg.i18n.class.top_image)
				:addClass(args.topimageclass)
				:cssText(args.topimagestyle)
				:wikitext(args.topimage)

			if args.topcaption then
				imageCell
					:tag('div')
						:addClass(cfg.i18n.class.top_caption)
						:cssText(args.topcaptionstyle)
						:wikitext(args.topcaption)
			end
		end

		if args.pretitle then
			root
				:tag('tr')
					:tag('td')
						:addClass(args.topimage and cfg.i18n.class.pretitle_with_top_image
							or cfg.i18n.class.pretitle)
						:addClass(args.pretitleclass)
						:cssText(args.basestyle)
						:cssText(args.pretitlestyle)
						:wikitext(args.pretitle)
		end
	else
		root
			:addClass(cfg.i18n.class.subgroup)
			:addClass(args.bodyclass or args.class)
			:cssText(args.bodystyle or args.style)
	end

	if args.title then
		if child then
			root
				:wikitext(args.title)
		else
			root
				:tag('tr')
					:tag('th')
						:addClass(args.pretitle and cfg.i18n.class.title_with_pretitle
							or cfg.i18n.class.title)
						:addClass(args.titleclass)
						:cssText(args.basestyle)
						:cssText(args.titlestyle)
						:wikitext(args.title)
		end
	end

	if args.image then
		local imageCell = root:tag('tr'):tag('td')

		imageCell
			:addClass(cfg.i18n.class.image)
			:addClass(args.imageclass)
			:cssText(args.imagestyle)
			:wikitext(args.image)

		if args.caption then
			imageCell
				:tag('div')
					:addClass(cfg.i18n.class.caption)
					:cssText(args.captionstyle)
					:wikitext(args.caption)
		end
	end

	if args.above then
		root
			:tag('tr')
				:tag('td')
					:addClass(cfg.i18n.class.above)
					:addClass(args.aboveclass)
					:cssText(args.abovestyle)
					:newline() -- newline required for bullet-points to work
					:wikitext(args.above)
	end

	local rowNums = {}
	for k, v in pairs(args) do
		k = '' .. k
		local num = k:match('^heading(%d+)$') or k:match('^content(%d+)$')
		if num then table.insert(rowNums, tonumber(num)) end
	end
	table.sort(rowNums)
	-- remove duplicates from the list (e.g. 3 will be duplicated if both heading3
	-- and content3 are specified)
	for i = #rowNums, 1, -1 do
		if rowNums[i] == rowNums[i - 1] then
			table.remove(rowNums, i)
		end
	end

	for i, num in ipairs(rowNums) do
		local heading = args['heading' .. num]
		if heading then
			root
				:tag('tr')
					:tag('th')
						:addClass(cfg.i18n.class.heading)
						:addClass(args.headingclass)
						:addClass(args['heading' .. num .. 'class'])
						:cssText(args.basestyle)
						:cssText(args.headingstyle)
						:cssText(args['heading' .. num .. 'style'])
						:newline()
						:wikitext(heading)
		end

		local content = args['content' .. num]
		if content then
			root
				:tag('tr')
					:tag('td')
						:addClass(hasSubgroup(content) and cfg.i18n.class.content_with_subgroup
							or cfg.i18n.class.content)
						:addClass(args.contentclass)
						:addClass(args['content' .. num .. 'class'])
						:cssText(args.contentstyle)
						:cssText(args['content' .. num .. 'style'])
						:newline()
						:wikitext(content)
						:done()
					 -- Without a linebreak after the </td>, a nested list like
					 -- "* {{hlist| ...}}" doesn't parse correctly.
					:newline()
		end
	end

	if args.below then
		root
			:tag('tr')
				:tag('td')
					:addClass(cfg.i18n.class.below)
					:addClass(args.belowclass)
					:cssText(args.belowstyle)
					:newline()
					:wikitext(args.below)
	end

	if not child then
		if args.navbar ~= cfg.i18n.navbar_none and args.navbar ~= cfg.i18n.navbar_off and
			(args.name or frame:getParent():getTitle():gsub(cfg.i18n.pattern.sandbox, '') ~=
			cfg.i18n.title_not_to_add_navbar) then
			root
				:tag('tr')
					:tag('td')
						:addClass(cfg.i18n.class.navbar)
						:cssText(args.navbarstyle)
						:wikitext(require('Module:Navbar')._navbar{
							args.name,
							mini = 1,
							fontstyle = args.navbarfontstyle
						})
		end
	end
	
	local base_templatestyles = frame:extensionTag{
		name = 'templatestyles', args = { src = cfg.i18n.templatestyles }
	}
	
	local templatestyles = ''
	if args['templatestyles'] and args['templatestyles'] ~= '' then
		templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = args['templatestyles'] }
		}
	end
	
	local child_templatestyles = ''
	if args['child templatestyles'] and args['child templatestyles'] ~= '' then
		child_templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = args['child templatestyles'] }
		}
	end
	
	local grandchild_templatestyles = ''
	if args['grandchild templatestyles'] and args['grandchild templatestyles'] ~= '' then
		grandchild_templatestyles = frame:extensionTag{
			name = 'templatestyles', args = { src = args['grandchild templatestyles'] }
		}
	end

	return table.concat({
		base_templatestyles,
		templatestyles,
		child_templatestyles,
		grandchild_templatestyles,
		tostring(root),
		(child and cfg.i18n.category.child or ''),
		categorizeTemplatesWithInlineStyles(args)
	})
end

local function list_title(args, is_centered_list_titles, num)
	
	local title_text = trimAndAddAutomaticNewline(args['list' .. num .. 'title']
		or cfg.i18n.default_list_title)

	local title
	if is_centered_list_titles then
		-- collapsible can be finicky, so provide some CSS/HTML to support
		title = mw.html.create('div')
			:addClass(cfg.i18n.class.list_title_centered)
			:wikitext(title_text)
	else
		title = mw.html.create()
			:wikitext(title_text)
	end
		
	local title_container = mw.html.create('div')
		:addClass(cfg.i18n.class.list_title)
		-- don't /need/ a listnumtitleclass because you can do
		-- .templateclass .listnumclass .sidebar-list-title
		:addClass(args.listtitleclass)
		:cssText(args.basestyle)
		:cssText(args.listtitlestyle)
		:cssText(args['list' .. num .. 'titlestyle'])
		:node(title)
		:done()
	
	return title_container
end

--[[
Main entry point for sidebar with collapsible lists.
Does the work of creating the collapsible lists themselves and including them
into the args.
]]
function p.collapsible(frame)
	local args = getArgs(frame)
	if not args.name and
		frame:getParent():getTitle():gsub(cfg.i18n.pattern.collapse_sandbox, '') ==
		cfg.i18n.collapse_title_not_to_add_navbar then
		args.navbar = cfg.i18n.navbar_none
	end

	local contentArgs = {}
	
	local is_centered_list_titles
	if args['centered list titles'] and args['centered list titles'] ~= '' then
		is_centered_list_titles = true
	else
		is_centered_list_titles = false
	end

	for k, v in pairs(args) do
		local num = string.match(k, '^list(%d+)$')
		if num then
			local expand = args.expanded and
				(args.expanded == 'all' or args.expanded == args['list' .. num .. 'name'])
			local row = mw.html.create('div')
			row
				:addClass(cfg.i18n.class.list)
				:addClass('mw-collapsible')
				:addClass((not expand) and 'mw-collapsed' or nil)
				:addClass(args['list' .. num .. 'class'])
				:cssText(args.listframestyle)
				:cssText(args['list' .. num .. 'framestyle'])
				:node(list_title(args, is_centered_list_titles, num))
				:tag('div')
					:addClass(cfg.i18n.class.list_content)
					:addClass('mw-collapsible-content')
					-- don't /need/ a listnumstyleclass because you can do
					-- .templatename .listnumclass .sidebar-list
					:addClass(args.listclass)
					:cssText(args.liststyle)
					:cssText(args['list' .. num .. 'style'])
					:wikitext(trimAndAddAutomaticNewline(args['list' .. num]))

			contentArgs['content' .. num] = tostring(row)
		end
	end

	for k, v in pairs(contentArgs) do
		args[k] = v
	end

	return p.sidebar(frame, args, cfg.i18n.class.collapse)
end

return p