Module:Sidebar: Difference between revisions

From TEPwiki, Urth's Encyclopedia
Jump to navigation Jump to search
Content added Content deleted
m (Changed protection level of Module:Sidebar: High-risk Lua module: allow template editors ([Edit=Allow only template editors and admins] (indefinite) [Move=Allow only template editors and admins] (indefinite)))
m (1 revision imported from wikipedia:Module:Sidebar)
 
(30 intermediate revisions by 12 users not shown)
Line 1: Line 1:
require('strict')
--
local cfg = mw.loadData('Module:Sidebar/configuration')
-- This module implements {{Sidebar}}

--
local p = {}
local p = {}

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

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


--[[
local function _sidebar(args)
Finds whether a sidebar has a subgroup sidebar.
local root = HtmlBuilder.create('table')
]]
local function hasSubgroup(s)
root
if mw.ustring.find(s, cfg.i18n.pattern.subgroup) then
.addClass('vertical-navbox')
return true
.addClass(args.wraplinks ~= 'true' and 'nowraplinks')
else
.addClass(args.bodyclass or args.class)
return false
.attr('cellspacing', args.cellspacing or 5)
end
.attr('cellpadding', args.cellpadding or 0)
end
.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)


local function has_navbar(navbar_mode, sidebar_name)
if args.outertitle then
return navbar_mode ~= cfg.i18n.navbar_none and
root
navbar_mode ~= cfg.i18n.navbar_off and
.tag('caption')
(
.addClass(args.outertitleclass)
sidebar_name or
.css('padding-bottom', '0.2em')
mw.getCurrentFrame():getParent():getTitle():gsub(cfg.i18n.pattern.sandbox, '') ~=
.css('font-size', '125%')
cfg.i18n.title_not_to_add_navbar
.css('line-height', '1.2em')
)
.css('font-weight', 'bold')
end
.cssText(args.outertitlestyle)
.wikitext(args.outertitle)
end


local function has_list_class(args, htmlclass)
if args.topimage then
local patterns = {
local imageCell = root.tag('tr').tag('td')
'^' .. htmlclass .. '$',
'%s' .. htmlclass .. '$',
imageCell
'^' .. htmlclass .. '%s',
.addClass(args.topimageclass)
'%s' .. htmlclass .. '%s'
.css('padding', '0.4em 0')
}
.cssText(args.topimagestyle)
.wikitext(args.topimage)
for arg, value in pairs(args) do
if type(arg) == 'string' and mw.ustring.find(arg, 'class') then
if args.topcaption then
for _, pattern in ipairs(patterns) do
imageCell
if mw.ustring.find(args[arg] or '', pattern) then
.tag('div')
return true
.css('padding-top', '0.2em')
end
.css('line-height', '1.2em')
end
.cssText(args.topcaptionstyle)
end
.wikitext(args.topcaption)
end
end
return false
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


-- there are a lot of list classes in the wild, so we add their TemplateStyles
if args.title then
local function add_list_styles(args)
root
local frame = mw.getCurrentFrame()
.tag('tr')
local function add_list_templatestyles(htmlclass, templatestyles)
.tag('th')
if has_list_class(args, htmlclass) then
.addClass(args.titleclass)
return frame:extensionTag{
.cssText(args.basestyle)
name = 'templatestyles', args = { src = templatestyles }
.css('padding', '0.2em 0.4em 0.2em')
}
.css('padding-top', args.pretitle and 0)
else
.css('font-size', '145%')
return ''
.css('line-height', '1.2em')
end
.cssText(args.titlestyle)
end
.wikitext(args.title)
end
local plainlist_styles = add_list_templatestyles('plainlist', 'Plainlist/styles.css')
local hlist_styles = add_list_templatestyles('hlist', 'Hlist/styles.css')
-- 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
if has_navbar(args.navbar, args.name) and hlist_styles == '' then
hlist_styles = frame:extensionTag{
name = 'templatestyles', args = { src = 'Hlist/styles.css' }
}
end
-- hlist -> plainlist is best-effort to preserve old Common.css ordering. [hlist_note]
return hlist_styles .. plainlist_styles
end


-- work around [[phab:T303378]]
if args.image then
-- for each arg: find all the templatestyles strip markers, insert them into a
local imageCell = root.tag('tr').tag('td')
-- table. then remove all templatestyles markers from the arg
local function move_hiding_templatestyles(args)
imageCell
local gfind = string.gfind
.addClass(args.imageclass)
local gsub = string.gsub
.css('padding', '0.2em 0 0.4em')
local templatestyles_markers = {}
.cssText(args.imagestyle)
local strip_marker_pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)'
.wikitext(args.image)
for k, arg in pairs(args) do
for marker in gfind(arg, strip_marker_pattern) do
if args.caption then
table.insert(templatestyles_markers, marker)
imageCell
end
.tag('div')
args[k] = gsub(arg, strip_marker_pattern, '')
.css('padding-top', '0.2em')
end
.css('line-height', '1.2em')
return templatestyles_markers
.cssText(args.captionstyle)
end
.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 = {}
Main sidebar function. Takes the frame, args, and an optional collapsibleClass.
for k, v in pairs(args) do
The collapsibleClass is and should be used only for sidebars with collapsible
k = '' .. k
lists, as in p.collapsible.
local num = k:match('^heading(%d+)$') or k:match('^content(%d+)$')
]]
if num then table.insert(rowNums, tonumber(num)) end
function p.sidebar(frame, args, collapsibleClass)
end
if not args then
table.sort(rowNums)
args = getArgs(frame)
-- remove duplicates from the list (e.g. 3 will be duplicated if both heading3 and content3 are specified)
end
for i = #rowNums, 1, -1 do
local hiding_templatestyles = table.concat(move_hiding_templatestyles(args))
if rowNums[i] == rowNums[i - 1] then
local root = mw.html.create()
table.remove(rowNums, i)
local child = args.child and mw.text.trim(args.child) == cfg.i18n.child_yes
end
end


root = root:tag('table')
for i, num in ipairs(rowNums) do
if not child then
local heading = args['heading' .. num]
root
if heading then
:addClass(cfg.i18n.class.sidebar)
root
-- force collapsibleclass to be sidebar-collapse otherwise output nothing
.tag('tr')
:addClass(collapsibleClass == cfg.i18n.class.collapse and cfg.i18n.class.collapse or nil)
.tag('th')
:addClass('nomobile')
.addClass(args.headingclass)
:addClass(args.float == cfg.i18n.float_none and cfg.i18n.class.float_none or nil)
.css('padding', '0.1em')
:addClass(args.float == cfg.i18n.float_left and cfg.i18n.class.float_left or nil)
.cssText(args.basestyle)
:addClass(args.wraplinks ~= cfg.i18n.wrap_true and cfg.i18n.class.wraplinks or nil)
.cssText(args.headingstyle)
:addClass(args.bodyclass or args.class)
.cssText(args['heading' .. num .. 'style'])
:css('width', args.width or nil)
.wikitext(heading)
:cssText(args.bodystyle or args.style)
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
if args.outertitle then
root
root
.tag('tr')
:tag('caption')
:addClass(cfg.i18n.class.outer_title)
.tag('td')
.addClass(args.belowclass)
:addClass(args.outertitleclass)
:cssText(args.outertitlestyle)
.css('padding', '0.3em 0.4em 0.3em')
:wikitext(args.outertitle)
.css('font-weight', 'bold')
end
.cssText(args.belowstyle)
.newline()
.wikitext(args.below)
end


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


imageCell
return tostring(root)
: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 and has_navbar(args.navbar, args.name) 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
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({
add_list_styles(args), -- see [hlist_note] above about ordering
base_templatestyles,
templatestyles,
child_templatestyles,
grandchild_templatestyles,
hiding_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 '')
local title_text = trimAndAddAutomaticNewline(args['list' .. num .. 'title']
args.belowstyle = 'border-top: 1px solid #aaa; border-bottom: 1px solid #aaa;' .. (args.belowstyle or '')
or cfg.i18n.default_list_title)
args.navbarstyle = 'padding-top: 0.6em;' .. (args.navbarstyle or args.tnavbarstyle or '')
local contentArgs = {}
for k, v in pairs(args) do
local num = ('' .. k):match('^list(%d+)$')
if num then
local expand = args.expanded and (args.expanded == 'all' or args.expanded == args['list' .. num .. 'name'])
local row = HtmlBuilder.create('div')
row
.addClass('NavFrame')
.addClass((not expand) and 'collapsed')
.css('border', 'none')
.css('padding', 0)
.cssText(args.listframestyle)
.cssText(args['list' .. num .. 'framestyle'])
.tag('div')
.addClass('NavHead')
.addClass(args.listtitleclass)
.css('font-size', '105%')
.css('background', 'transparent')
.css('text-align', 'left')
.cssText(args.basestyle)
.cssText(args.listtitlestyle)
.cssText(args['list' .. num .. 'titlestyle'])
.wikitext(trimAndAddAutomaticNewline(args['list' .. num .. 'title'] or 'List'))
.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'])
.wikitext(trimAndAddAutomaticNewline(args['list' .. num]))
contentArgs['content' .. num] = tostring(row)
end
end


local title
for k, v in pairs(contentArgs) do
if is_centered_list_titles then
args[k] = v
-- collapsible can be finicky, so provide some CSS/HTML to support
end
title = mw.html.create('div')
:addClass(cfg.i18n.class.list_title_centered)
return _sidebar(args)
: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
end

--[[
function makeWrapper(func)
Main entry point for sidebar with collapsible lists.
return function(frame)
Does the work of creating the collapsible lists themselves and including them
local origArgs
into the args.
if frame == mw.getCurrentFrame() then
]]
-- We're being called via #invoke. If the invoking template passed any args, use
function p.collapsible(frame)
-- them. Otherwise, use the args that were passed into the template.
local args = getArgs(frame)
origArgs = frame:getParent().args
if not args.name and
for k, v in pairs(frame.args) do
frame:getParent():getTitle():gsub(cfg.i18n.pattern.collapse_sandbox, '') ==
origArgs = frame.args
cfg.i18n.collapse_title_not_to_add_navbar then
break
args.navbar = cfg.i18n.navbar_none
end
end
else

-- We're being called from another module or from the debug console, so assume
local contentArgs = {}
-- the args are passed in directly.
origArgs = frame
local is_centered_list_titles = false
end
if args['centered list titles'] and args['centered list titles'] ~= '' then
is_centered_list_titles = true
-- ParserFunctions considers the empty string to be false, so to preserve the previous
end
-- behavior of the template, change any empty arguments to nil, so Lua will consider

-- them false too.
local args = {}
for k, v in pairs(args) do
local num = string.match(k, '^list(%d+)$')
for k, v in pairs(origArgs) do
if v ~= '' then
if num then
local expand = args.expanded and
args[k] = v
(args.expanded == 'all' or args.expanded == args['list' .. num .. 'name'])
end
local row = mw.html.create('div')
end
row
:addClass(cfg.i18n.class.list)
return func(args)
:addClass('mw-collapsible')
end
: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 01:39, 6 March 2023

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

require('strict')
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

local function has_navbar(navbar_mode, sidebar_name)
	return navbar_mode ~= cfg.i18n.navbar_none and
		navbar_mode ~= cfg.i18n.navbar_off and
		(
			sidebar_name or
			mw.getCurrentFrame():getParent():getTitle():gsub(cfg.i18n.pattern.sandbox, '') ~=
			cfg.i18n.title_not_to_add_navbar
		)
end

local function has_list_class(args, htmlclass)
	local patterns = {
		'^' .. htmlclass .. '$',
		'%s' .. htmlclass .. '$',
		'^' .. htmlclass .. '%s',
		'%s' .. htmlclass .. '%s'
	}
	
	for arg, value in pairs(args) do
		if type(arg) == 'string' and mw.ustring.find(arg, 'class') then
			for _, pattern in ipairs(patterns) do
				if mw.ustring.find(args[arg] or '', pattern) then
					return true
				end
			end
		end
	end
	return false
end

-- there are a lot of list classes in the wild, so we add their TemplateStyles
local function add_list_styles(args)
	local frame = mw.getCurrentFrame()
	local function add_list_templatestyles(htmlclass, templatestyles)
		if has_list_class(args, htmlclass) then
			return frame:extensionTag{
				name = 'templatestyles', args = { src = templatestyles }
			}
		else
			return ''
		end
	end
	
	local plainlist_styles = add_list_templatestyles('plainlist', 'Plainlist/styles.css')
	local hlist_styles = add_list_templatestyles('hlist', 'Hlist/styles.css')
	
	-- 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
	if has_navbar(args.navbar, args.name) and hlist_styles == '' then
		hlist_styles = frame:extensionTag{
			name = 'templatestyles', args = { src = 'Hlist/styles.css' }
		}
	end
	
	-- hlist -> plainlist is best-effort to preserve old Common.css ordering. [hlist_note]
	return hlist_styles .. plainlist_styles
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
local function move_hiding_templatestyles(args)
	local gfind = string.gfind
	local gsub = string.gsub
	local templatestyles_markers = {}
	local strip_marker_pattern = '(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)'
	for k, arg in pairs(args) do
		for marker in gfind(arg, strip_marker_pattern) do
			table.insert(templatestyles_markers, marker)
		end
		args[k] = gsub(arg, strip_marker_pattern, '')
	end
	return templatestyles_markers
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 hiding_templatestyles = table.concat(move_hiding_templatestyles(args))
	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 and has_navbar(args.navbar, args.name) 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
	
	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({
		add_list_styles(args), -- see [hlist_note] above about ordering
		base_templatestyles,
		templatestyles,
		child_templatestyles,
		grandchild_templatestyles,
		hiding_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 = false
	if args['centered list titles'] and args['centered list titles'] ~= '' then
		is_centered_list_titles = true
	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