From Wikipedia, the free encyclopedia

local p = {}

local navbar = require('Module:Navbar')._navbar

local cfg = mw.loadData('Module:Navbox/configuration')

local getArgs -- lazily initialized

local args

local format = string.format



local function striped(wikitext, border)

	-- Return wikitext with markers replaced for odd/even striping.

	-- Child (subgroup) navboxes are flagged with a category that is removed

	-- by parent navboxes. The result is that the category shows all pages

	-- where a child navbox is not contained in a parent navbox.

	local orphanCat = cfg.category.orphan

	if border == cfg.keyword.border_subgroup and argscfg.arg.orphan ~= cfg.keyword.orphan_yes then

		-- No change; striping occurs in outermost navbox.

		return wikitext .. orphanCat

	end

	local first, second = cfg.class.navbox_odd_part, cfg.class.navbox_even_part

	if argscfg.arg.evenodd then

		if argscfg.arg.evenodd == cfg.keyword.evenodd_swap then

			first, second = second, first

		else

			first = argscfg.arg.evenodd

			second = first

		end

	end

	local changer

	if first == second then

		changer = first

	else

		local index = 0

		changer = function (code)

			if code == '0' then

				-- Current occurrence is for a group before a nested table.

				-- Set it to first as a valid although pointless class.

				-- The next occurrence will be the first row after a title

				-- in a subgroup and will also be first.

				index = 0

				return first

			end

			index = index + 1

			return index % 2 == 1 and first or second

		end

	end

	local regex = orphanCat:gsub('([%[%]])', '%%%1')

	return (wikitext:gsub(regex, ''):gsub(cfg.marker.regex, changer)) -- () omits gsub count

end



local function processItem(item, nowrapitems)

	if item:sub(1, 2) == '{|' then

		-- Applying nowrap to lines in a table does not make sense.

		-- Add newlines to compensate for trim of x in |parm=x in a template.

		return '\n' .. item ..'\n'

	end

	if nowrapitems == cfg.keyword.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(cfg.pattern.nowrap) then

				line = format(cfg.nowrap_item, prefix, content)

			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 has_navbar()

	return argscfg.arg.navbar ~= cfg.keyword.navbar_off

		and argscfg.arg.navbar ~= cfg.keyword.navbar_plain

		and (

			argscfg.arg.name

			or mw.getCurrentFrame():getParent():getTitle():gsub(cfg.pattern.sandbox, '')

				~= cfg.pattern.navbox

		)

end



local function renderNavBar(titleCell)

	if has_navbar() then

		titleCell:wikitext(navbar{

			cfg.navbar.name = argscfg.arg.name],

			cfg.navbar.mini = 1,

			cfg.navbar.fontstyle = (argscfg.arg.basestyle or '') .. ';' ..

				(argscfg.arg.titlestyle or '') ..

				';background:none transparent;border:none;box-shadow:none;padding:0;'

		})

	end



end



local function renderTitleRow(tbl)

	if not argscfg.arg.title then return end



	local titleRow = tbl:tag('tr')



	local titleCell = titleRow:tag('th'):attr('scope', 'col')



	local titleColspan = 2

	if argscfg.arg.imageleft then titleColspan = titleColspan + 1 end

	if argscfg.arg.image then titleColspan = titleColspan + 1 end



	titleCell

		:cssText(argscfg.arg.basestyle])

		:cssText(argscfg.arg.titlestyle])

		:addClass(cfg.class.navbox_title)

		:attr('colspan', titleColspan)



	renderNavBar(titleCell)



	titleCell

		:tag('div')

			-- id for aria-labelledby attribute

			:attr('id', mw.uri.anchorEncode(argscfg.arg.title]))

			:addClass(argscfg.arg.titleclass])

			:css('font-size', '114%')

			:css('margin', '0 4em')

			:wikitext(processItem(argscfg.arg.title]))

end



local function getAboveBelowColspan()

	local ret = 2

	if argscfg.arg.imageleft then ret = ret + 1 end

	if argscfg.arg.image then ret = ret + 1 end

	return ret

end



local function renderAboveRow(tbl)

	if not argscfg.arg.above then return end



	tbl:tag('tr')

		:tag('td')

			:addClass(cfg.class.navbox_abovebelow)

			:addClass(argscfg.arg.aboveclass])

			:cssText(argscfg.arg.basestyle])

			:cssText(argscfg.arg.abovestyle])

			:attr('colspan', getAboveBelowColspan())

			:tag('div')

				-- id for aria-labelledby attribute, if no title

				:attr('id', (not argscfg.arg.title]) and mw.uri.anchorEncode(argscfg.arg.above]) or nil)

				:wikitext(processItem(argscfg.arg.above], argscfg.arg.nowrapitems]))

end



local function renderBelowRow(tbl)

	if not argscfg.arg.below then return end



	tbl:tag('tr')

		:tag('td')

			:addClass(cfg.class.navbox_abovebelow)

			:addClass(argscfg.arg.belowclass])

			:cssText(argscfg.arg.basestyle])

			:cssText(argscfg.arg.belowstyle])

			:attr('colspan', getAboveBelowColspan())

			:tag('div')

				:wikitext(processItem(argscfg.arg.below], argscfg.arg.nowrapitems]))

end



local function renderListRow(tbl, index, listnum, listnums_size)

	local row = tbl:tag('tr')



	if index == 1 and argscfg.arg.imageleft then

		row

			:tag('td')

				:addClass(cfg.class.noviewer)

				:addClass(cfg.class.navbox_image)

				:addClass(argscfg.arg.imageclass])

				:css('width', '1px')               -- Minimize width

				:css('padding', '0 2px 0 0')

				:cssText(argscfg.arg.imageleftstyle])

				:attr('rowspan', listnums_size)

				:tag('div')

					:wikitext(processItem(argscfg.arg.imageleft]))

	end



	local group_and_num = format(cfg.arg.group_and_num, listnum)

	local groupstyle_and_num = format(cfg.arg.groupstyle_and_num, listnum)

	if argsgroup_and_num then

		local groupCell = row:tag('th')



		-- id for aria-labelledby attribute, if lone group with no title or above

		if listnum == 1 and not (argscfg.arg.title or argscfg.arg.above or argscfg.arg.group2]) then

			groupCell

				:attr('id', mw.uri.anchorEncode(argscfg.arg.group1]))

		end



		groupCell

			:attr('scope', 'row')

			:addClass(cfg.class.navbox_group)

			:addClass(argscfg.arg.groupclass])

			:cssText(argscfg.arg.basestyle])

			-- If groupwidth not specified, minimize width

			:css('width', argscfg.arg.groupwidth or '1%')



		groupCell

			:cssText(argscfg.arg.groupstyle])

			:cssText(argsgroupstyle_and_num])

			:wikitext(argsgroup_and_num])

	end



	local listCell = row:tag('td')



	if argsgroup_and_num then

		listCell

			:addClass(cfg.class.navbox_list_with_group)

	else

		listCell:attr('colspan', 2)

	end



	if not argscfg.arg.groupwidth then

		listCell:css('width', '100%')

	end



	local rowstyle  -- usually nil so cssText(rowstyle) usually adds nothing

	if index % 2 == 1 then

		rowstyle = argscfg.arg.oddstyle

	else

		rowstyle = argscfg.arg.evenstyle

	end



	local list_and_num = format(cfg.arg.list_and_num, listnum)

	local listText = argslist_and_num

	local oddEven = cfg.marker.oddeven

	if listText:sub(1, 12) == '</div><table' then

		-- Assume list text is for a subgroup navbox so no automatic striping for this row.

		oddEven = listText:find(cfg.pattern.navbox_title) and cfg.marker.restart or cfg.class.navbox_odd_part

	end



	local liststyle_and_num = format(cfg.arg.liststyle_and_num, listnum)

	local listclass_and_num = format(cfg.arg.listclass_and_num, listnum)

	listCell

		:css('padding', '0')

		:cssText(argscfg.arg.liststyle])

		:cssText(rowstyle)

		:cssText(argsliststyle_and_num])

		:addClass(cfg.class.navbox_list)

		:addClass(cfg.class.navbox_part .. oddEven)

		:addClass(argscfg.arg.listclass])

		:addClass(argslistclass_and_num])

		:tag('div')

			:css('padding',

				(index == 1 and argscfg.arg.list1padding]) or argscfg.arg.listpadding or '0 0.25em'

			)

			:wikitext(processItem(listText, argscfg.arg.nowrapitems]))



	if index == 1 and argscfg.arg.image then

		row

			:tag('td')

				:addClass(cfg.class.noviewer)

				:addClass(cfg.class.navbox_image)

				:addClass(argscfg.arg.imageclass])

				:css('width', '1px')               -- Minimize width

				:css('padding', '0 0 0 2px')

				:cssText(argscfg.arg.imagestyle])

				:attr('rowspan', listnums_size)

				:tag('div')

					:wikitext(processItem(argscfg.arg.image]))

	end

end



local function has_list_class(htmlclass)

	local patterns = {

		'^' .. htmlclass .. '$',

		'%s' .. htmlclass .. '$',

		'^' .. htmlclass .. '%s',

		'%s' .. htmlclass .. '%s'

	}

	

	for arg, _ in pairs(args) do

		if type(arg) == 'string' and mw.ustring.find(arg, cfg.pattern.class) then

			for _, pattern in ipairs(patterns) do

				if mw.ustring.find(argsarg 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()

	local frame = mw.getCurrentFrame()

	local function add_list_templatestyles(htmlclass, templatestyles)

		if has_list_class(htmlclass) then

			return frame:extensionTag{

				name = 'templatestyles', args = { src = templatestyles }

			}

		else

			return ''

		end

	end

	

	local hlist_styles = add_list_templatestyles('hlist', cfg.hlist_templatestyles)

	local plainlist_styles = add_list_templatestyles('plainlist', cfg.plainlist_templatestyles)

	

	-- 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() and hlist_styles == '' then

		hlist_styles = frame:extensionTag{

			name = 'templatestyles', args = { src = cfg.hlist_templatestyles }

		}

	end

	

	-- hlist -> plainlist is best-effort to preserve old Common.css ordering.

	-- this ordering is not a guarantee because most navboxes will emit only

	-- one of these classes [hlist_note]

	return hlist_styles .. plainlist_styles

end



local function needsHorizontalLists(border)

	if border == cfg.keyword.border_subgroup or argscfg.arg.tracking == cfg.keyword.tracking_no then

		return false

	end

	return not has_list_class(cfg.pattern.hlist) and not has_list_class(cfg.pattern.plainlist)

end



local function hasBackgroundColors()

	for _, key in ipairs({cfg.arg.titlestyle, cfg.arg.groupstyle,

		cfg.arg.basestyle, cfg.arg.abovestyle, cfg.arg.belowstyle}) do

		if tostring(argskey]):find('background', 1, true) then

			return true

		end

	end

	return false

end



local function hasBorders()

	for _, key in ipairs({cfg.arg.groupstyle, cfg.arg.basestyle,

		cfg.arg.abovestyle, cfg.arg.belowstyle}) do

		if tostring(argskey]):find('border', 1, true) then

			return true

		end

	end

	return false

end



local function isIllegible()

	local styleratio = require('Module:Color contrast')._styleratio

	for key, style in pairs(args) do

		if tostring(key):match(cfg.pattern.style) then

			if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then

				return true

			end

		end

	end

	return false

end



local function getTrackingCategories(border)

	local cats = {}

	if needsHorizontalLists(border) then table.insert(cats, cfg.category.horizontal_lists) end

	if hasBackgroundColors() then table.insert(cats, cfg.category.background_colors) end

	if isIllegible() then table.insert(cats, cfg.category.illegible) end

	if hasBorders() then table.insert(cats, cfg.category.borders) end

	return cats

end



local function renderTrackingCategories(builder, border)

	local title = mw.title.getCurrentTitle()

	if title.namespace ~= 10 then return end -- not in template space

	local subpage = title.subpageText

	if subpage == cfg.keyword.subpage_doc or subpage == cfg.keyword.subpage_sandbox

		or subpage == cfg.keyword.subpage_testcases then return end



	for _, cat in ipairs(getTrackingCategories(border)) do

		builder:wikitext('[[Category:' .. cat .. ']]')

	end

end



local function renderMainTable(border, listnums)

	local tbl = mw.html.create('table')

		:addClass(cfg.class.nowraplinks)

		:addClass(argscfg.arg.bodyclass])



	local state = argscfg.arg.state

	if argscfg.arg.title and state ~= cfg.keyword.state_plain and state ~= cfg.keyword.state_off then

		if state == cfg.keyword.state_collapsed then

			state = cfg.class.collapsed

		end

		tbl

			:addClass(cfg.class.collapsible)

			:addClass(state or cfg.class.autocollapse)

	end



	tbl:css('border-spacing', 0)

	if border == cfg.keyword.border_subgroup or border == cfg.keyword.border_none then

		tbl

			:addClass(cfg.class.navbox_subgroup)

			:cssText(argscfg.arg.bodystyle])

			:cssText(argscfg.arg.style])

	else  -- regular navbox - bodystyle and style will be applied to the wrapper table

		tbl

			:addClass(cfg.class.navbox_inner)

			:css('background', 'transparent')

			:css('color', 'inherit')

	end

	tbl:cssText(argscfg.arg.innerstyle])



	renderTitleRow(tbl)

	renderAboveRow(tbl)

	local listnums_size = #listnums

	for i, listnum in ipairs(listnums) do

		renderListRow(tbl, i, listnum, listnums_size)

	end

	renderBelowRow(tbl)



	return tbl

end



local function add_navbox_styles(hiding_templatestyles)

	local frame = mw.getCurrentFrame()

	-- This is a lambda so that it doesn't need the frame as a parameter

	local function add_user_styles(templatestyles)

		if templatestyles and templatestyles ~= '' then

			return frame:extensionTag{

				name = 'templatestyles', args = { src = templatestyles }

			}

		end

		return ''

	end



	-- get templatestyles. load base from config so that Lua only needs to do

	-- the work once of parser tag expansion

	local base_templatestyles = cfg.templatestyles

	local templatestyles = add_user_styles(argscfg.arg.templatestyles])

	local child_templatestyles = add_user_styles(argscfg.arg.child_templatestyles])



	-- The 'navbox-styles' div exists to wrap the styles to work around T200206

	-- more elegantly. Instead of combinatorial rules, this ends up being linear

	-- number of CSS rules.

	return mw.html.create('div')

		:addClass(cfg.class.navbox_styles)

		:wikitext(

			add_list_styles() .. -- see [hlist_note] applied to 'before base_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

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

		argsk = gsub(arg, strip_marker_pattern, '')

	end

	return templatestyles_markers

end



function p._navbox(navboxArgs)

	args = navboxArgs

	local hiding_templatestyles = move_hiding_templatestyles(args)

	local listnums = {}



	for k, _ in pairs(args) do

		if type(k) == 'string' then

			local listnum = k:match(cfg.pattern.listnum)

			if listnum then table.insert(listnums, tonumber(listnum)) end

		end

	end

	table.sort(listnums)



	local border = mw.text.trim(argscfg.arg.border or args1 or '')

	if border == cfg.keyword.border_child then

		border = cfg.keyword.border_subgroup

	end



	-- render the main body of the navbox

	local tbl = renderMainTable(border, listnums)



	local res = mw.html.create()

	-- render the appropriate wrapper for the navbox, based on the border param



	if border == cfg.keyword.border_none then

		res:node(add_navbox_styles(hiding_templatestyles))

		local nav = res:tag('div')

			:attr('role', 'navigation')

			:node(tbl)

		-- aria-labelledby title, otherwise above, otherwise lone group

		if argscfg.arg.title or argscfg.arg.above or (argscfg.arg.group1

			and not argscfg.arg.group2]) then

			nav:attr(

				'aria-labelledby',

				mw.uri.anchorEncode(

					argscfg.arg.title or argscfg.arg.above or argscfg.arg.group1

				)

			)

		else

			nav:attr('aria-label', cfg.aria_label)

		end

	elseif border == cfg.keyword.border_subgroup then

		-- We assume that this navbox is being rendered in a list cell of a

		-- parent navbox, and is therefore inside a div with padding:0em 0.25em.

		-- We start with a </div> to avoid the padding being applied, and at the

		-- end add a <div> to balance out the parent's </div>

		res

			:wikitext('</div>')

			:node(tbl)

			:wikitext('<div>')

	else

		res:node(add_navbox_styles(hiding_templatestyles))

		local nav = res:tag('div')

			:attr('role', 'navigation')

			:addClass(cfg.class.navbox)

			:addClass(argscfg.arg.navboxclass])

			:cssText(argscfg.arg.bodystyle])

			:cssText(argscfg.arg.style])

			:css('padding', '3px')

			:node(tbl)

		-- aria-labelledby title, otherwise above, otherwise lone group

		if argscfg.arg.title or argscfg.arg.above

			or (argscfg.arg.group1 and not argscfg.arg.group2]) then

			nav:attr(

				'aria-labelledby',

				mw.uri.anchorEncode(argscfg.arg.title or argscfg.arg.above or argscfg.arg.group1])

			)

		else

			nav:attr('aria-label', cfg.aria_label)

		end

	end



	if (argscfg.arg.nocat or cfg.keyword.nocat_false):lower() == cfg.keyword.nocat_false then

		renderTrackingCategories(res, border)

	end

	return striped(tostring(res), border)

end



function p.navbox(frame)

	if not getArgs then

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

	end

	args = getArgs(frame, {wrappers = {cfg.pattern.navbox}})



	-- Read the arguments in the order they'll be output in, to make references

	-- number in the right order.

	local _

	_ = argscfg.arg.title

	_ = argscfg.arg.above

	-- Limit this to 20 as covering 'most' cases (that's a SWAG) and because

	-- iterator approach won't work here

	for i = 1, 20 do

		_ = argsformat(cfg.arg.group_and_num, i)]

		_ = argsformat(cfg.arg.list_and_num, i)]

	end

	_ = argscfg.arg.below



	return p._navbox(args)

end



return p
From Wikipedia, the free encyclopedia

local p = {}

local navbar = require('Module:Navbar')._navbar

local cfg = mw.loadData('Module:Navbox/configuration')

local getArgs -- lazily initialized

local args

local format = string.format



local function striped(wikitext, border)

	-- Return wikitext with markers replaced for odd/even striping.

	-- Child (subgroup) navboxes are flagged with a category that is removed

	-- by parent navboxes. The result is that the category shows all pages

	-- where a child navbox is not contained in a parent navbox.

	local orphanCat = cfg.category.orphan

	if border == cfg.keyword.border_subgroup and argscfg.arg.orphan ~= cfg.keyword.orphan_yes then

		-- No change; striping occurs in outermost navbox.

		return wikitext .. orphanCat

	end

	local first, second = cfg.class.navbox_odd_part, cfg.class.navbox_even_part

	if argscfg.arg.evenodd then

		if argscfg.arg.evenodd == cfg.keyword.evenodd_swap then

			first, second = second, first

		else

			first = argscfg.arg.evenodd

			second = first

		end

	end

	local changer

	if first == second then

		changer = first

	else

		local index = 0

		changer = function (code)

			if code == '0' then

				-- Current occurrence is for a group before a nested table.

				-- Set it to first as a valid although pointless class.

				-- The next occurrence will be the first row after a title

				-- in a subgroup and will also be first.

				index = 0

				return first

			end

			index = index + 1

			return index % 2 == 1 and first or second

		end

	end

	local regex = orphanCat:gsub('([%[%]])', '%%%1')

	return (wikitext:gsub(regex, ''):gsub(cfg.marker.regex, changer)) -- () omits gsub count

end



local function processItem(item, nowrapitems)

	if item:sub(1, 2) == '{|' then

		-- Applying nowrap to lines in a table does not make sense.

		-- Add newlines to compensate for trim of x in |parm=x in a template.

		return '\n' .. item ..'\n'

	end

	if nowrapitems == cfg.keyword.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(cfg.pattern.nowrap) then

				line = format(cfg.nowrap_item, prefix, content)

			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 has_navbar()

	return argscfg.arg.navbar ~= cfg.keyword.navbar_off

		and argscfg.arg.navbar ~= cfg.keyword.navbar_plain

		and (

			argscfg.arg.name

			or mw.getCurrentFrame():getParent():getTitle():gsub(cfg.pattern.sandbox, '')

				~= cfg.pattern.navbox

		)

end



local function renderNavBar(titleCell)

	if has_navbar() then

		titleCell:wikitext(navbar{

			cfg.navbar.name = argscfg.arg.name],

			cfg.navbar.mini = 1,

			cfg.navbar.fontstyle = (argscfg.arg.basestyle or '') .. ';' ..

				(argscfg.arg.titlestyle or '') ..

				';background:none transparent;border:none;box-shadow:none;padding:0;'

		})

	end



end



local function renderTitleRow(tbl)

	if not argscfg.arg.title then return end



	local titleRow = tbl:tag('tr')



	local titleCell = titleRow:tag('th'):attr('scope', 'col')



	local titleColspan = 2

	if argscfg.arg.imageleft then titleColspan = titleColspan + 1 end

	if argscfg.arg.image then titleColspan = titleColspan + 1 end



	titleCell

		:cssText(argscfg.arg.basestyle])

		:cssText(argscfg.arg.titlestyle])

		:addClass(cfg.class.navbox_title)

		:attr('colspan', titleColspan)



	renderNavBar(titleCell)



	titleCell

		:tag('div')

			-- id for aria-labelledby attribute

			:attr('id', mw.uri.anchorEncode(argscfg.arg.title]))

			:addClass(argscfg.arg.titleclass])

			:css('font-size', '114%')

			:css('margin', '0 4em')

			:wikitext(processItem(argscfg.arg.title]))

end



local function getAboveBelowColspan()

	local ret = 2

	if argscfg.arg.imageleft then ret = ret + 1 end

	if argscfg.arg.image then ret = ret + 1 end

	return ret

end



local function renderAboveRow(tbl)

	if not argscfg.arg.above then return end



	tbl:tag('tr')

		:tag('td')

			:addClass(cfg.class.navbox_abovebelow)

			:addClass(argscfg.arg.aboveclass])

			:cssText(argscfg.arg.basestyle])

			:cssText(argscfg.arg.abovestyle])

			:attr('colspan', getAboveBelowColspan())

			:tag('div')

				-- id for aria-labelledby attribute, if no title

				:attr('id', (not argscfg.arg.title]) and mw.uri.anchorEncode(argscfg.arg.above]) or nil)

				:wikitext(processItem(argscfg.arg.above], argscfg.arg.nowrapitems]))

end



local function renderBelowRow(tbl)

	if not argscfg.arg.below then return end



	tbl:tag('tr')

		:tag('td')

			:addClass(cfg.class.navbox_abovebelow)

			:addClass(argscfg.arg.belowclass])

			:cssText(argscfg.arg.basestyle])

			:cssText(argscfg.arg.belowstyle])

			:attr('colspan', getAboveBelowColspan())

			:tag('div')

				:wikitext(processItem(argscfg.arg.below], argscfg.arg.nowrapitems]))

end



local function renderListRow(tbl, index, listnum, listnums_size)

	local row = tbl:tag('tr')



	if index == 1 and argscfg.arg.imageleft then

		row

			:tag('td')

				:addClass(cfg.class.noviewer)

				:addClass(cfg.class.navbox_image)

				:addClass(argscfg.arg.imageclass])

				:css('width', '1px')               -- Minimize width

				:css('padding', '0 2px 0 0')

				:cssText(argscfg.arg.imageleftstyle])

				:attr('rowspan', listnums_size)

				:tag('div')

					:wikitext(processItem(argscfg.arg.imageleft]))

	end



	local group_and_num = format(cfg.arg.group_and_num, listnum)

	local groupstyle_and_num = format(cfg.arg.groupstyle_and_num, listnum)

	if argsgroup_and_num then

		local groupCell = row:tag('th')



		-- id for aria-labelledby attribute, if lone group with no title or above

		if listnum == 1 and not (argscfg.arg.title or argscfg.arg.above or argscfg.arg.group2]) then

			groupCell

				:attr('id', mw.uri.anchorEncode(argscfg.arg.group1]))

		end



		groupCell

			:attr('scope', 'row')

			:addClass(cfg.class.navbox_group)

			:addClass(argscfg.arg.groupclass])

			:cssText(argscfg.arg.basestyle])

			-- If groupwidth not specified, minimize width

			:css('width', argscfg.arg.groupwidth or '1%')



		groupCell

			:cssText(argscfg.arg.groupstyle])

			:cssText(argsgroupstyle_and_num])

			:wikitext(argsgroup_and_num])

	end



	local listCell = row:tag('td')



	if argsgroup_and_num then

		listCell

			:addClass(cfg.class.navbox_list_with_group)

	else

		listCell:attr('colspan', 2)

	end



	if not argscfg.arg.groupwidth then

		listCell:css('width', '100%')

	end



	local rowstyle  -- usually nil so cssText(rowstyle) usually adds nothing

	if index % 2 == 1 then

		rowstyle = argscfg.arg.oddstyle

	else

		rowstyle = argscfg.arg.evenstyle

	end



	local list_and_num = format(cfg.arg.list_and_num, listnum)

	local listText = argslist_and_num

	local oddEven = cfg.marker.oddeven

	if listText:sub(1, 12) == '</div><table' then

		-- Assume list text is for a subgroup navbox so no automatic striping for this row.

		oddEven = listText:find(cfg.pattern.navbox_title) and cfg.marker.restart or cfg.class.navbox_odd_part

	end



	local liststyle_and_num = format(cfg.arg.liststyle_and_num, listnum)

	local listclass_and_num = format(cfg.arg.listclass_and_num, listnum)

	listCell

		:css('padding', '0')

		:cssText(argscfg.arg.liststyle])

		:cssText(rowstyle)

		:cssText(argsliststyle_and_num])

		:addClass(cfg.class.navbox_list)

		:addClass(cfg.class.navbox_part .. oddEven)

		:addClass(argscfg.arg.listclass])

		:addClass(argslistclass_and_num])

		:tag('div')

			:css('padding',

				(index == 1 and argscfg.arg.list1padding]) or argscfg.arg.listpadding or '0 0.25em'

			)

			:wikitext(processItem(listText, argscfg.arg.nowrapitems]))



	if index == 1 and argscfg.arg.image then

		row

			:tag('td')

				:addClass(cfg.class.noviewer)

				:addClass(cfg.class.navbox_image)

				:addClass(argscfg.arg.imageclass])

				:css('width', '1px')               -- Minimize width

				:css('padding', '0 0 0 2px')

				:cssText(argscfg.arg.imagestyle])

				:attr('rowspan', listnums_size)

				:tag('div')

					:wikitext(processItem(argscfg.arg.image]))

	end

end



local function has_list_class(htmlclass)

	local patterns = {

		'^' .. htmlclass .. '$',

		'%s' .. htmlclass .. '$',

		'^' .. htmlclass .. '%s',

		'%s' .. htmlclass .. '%s'

	}

	

	for arg, _ in pairs(args) do

		if type(arg) == 'string' and mw.ustring.find(arg, cfg.pattern.class) then

			for _, pattern in ipairs(patterns) do

				if mw.ustring.find(argsarg 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()

	local frame = mw.getCurrentFrame()

	local function add_list_templatestyles(htmlclass, templatestyles)

		if has_list_class(htmlclass) then

			return frame:extensionTag{

				name = 'templatestyles', args = { src = templatestyles }

			}

		else

			return ''

		end

	end

	

	local hlist_styles = add_list_templatestyles('hlist', cfg.hlist_templatestyles)

	local plainlist_styles = add_list_templatestyles('plainlist', cfg.plainlist_templatestyles)

	

	-- 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() and hlist_styles == '' then

		hlist_styles = frame:extensionTag{

			name = 'templatestyles', args = { src = cfg.hlist_templatestyles }

		}

	end

	

	-- hlist -> plainlist is best-effort to preserve old Common.css ordering.

	-- this ordering is not a guarantee because most navboxes will emit only

	-- one of these classes [hlist_note]

	return hlist_styles .. plainlist_styles

end



local function needsHorizontalLists(border)

	if border == cfg.keyword.border_subgroup or argscfg.arg.tracking == cfg.keyword.tracking_no then

		return false

	end

	return not has_list_class(cfg.pattern.hlist) and not has_list_class(cfg.pattern.plainlist)

end



local function hasBackgroundColors()

	for _, key in ipairs({cfg.arg.titlestyle, cfg.arg.groupstyle,

		cfg.arg.basestyle, cfg.arg.abovestyle, cfg.arg.belowstyle}) do

		if tostring(argskey]):find('background', 1, true) then

			return true

		end

	end

	return false

end



local function hasBorders()

	for _, key in ipairs({cfg.arg.groupstyle, cfg.arg.basestyle,

		cfg.arg.abovestyle, cfg.arg.belowstyle}) do

		if tostring(argskey]):find('border', 1, true) then

			return true

		end

	end

	return false

end



local function isIllegible()

	local styleratio = require('Module:Color contrast')._styleratio

	for key, style in pairs(args) do

		if tostring(key):match(cfg.pattern.style) then

			if styleratio{mw.text.unstripNoWiki(style)} < 4.5 then

				return true

			end

		end

	end

	return false

end



local function getTrackingCategories(border)

	local cats = {}

	if needsHorizontalLists(border) then table.insert(cats, cfg.category.horizontal_lists) end

	if hasBackgroundColors() then table.insert(cats, cfg.category.background_colors) end

	if isIllegible() then table.insert(cats, cfg.category.illegible) end

	if hasBorders() then table.insert(cats, cfg.category.borders) end

	return cats

end



local function renderTrackingCategories(builder, border)

	local title = mw.title.getCurrentTitle()

	if title.namespace ~= 10 then return end -- not in template space

	local subpage = title.subpageText

	if subpage == cfg.keyword.subpage_doc or subpage == cfg.keyword.subpage_sandbox

		or subpage == cfg.keyword.subpage_testcases then return end



	for _, cat in ipairs(getTrackingCategories(border)) do

		builder:wikitext('[[Category:' .. cat .. ']]')

	end

end



local function renderMainTable(border, listnums)

	local tbl = mw.html.create('table')

		:addClass(cfg.class.nowraplinks)

		:addClass(argscfg.arg.bodyclass])



	local state = argscfg.arg.state

	if argscfg.arg.title and state ~= cfg.keyword.state_plain and state ~= cfg.keyword.state_off then

		if state == cfg.keyword.state_collapsed then

			state = cfg.class.collapsed

		end

		tbl

			:addClass(cfg.class.collapsible)

			:addClass(state or cfg.class.autocollapse)

	end



	tbl:css('border-spacing', 0)

	if border == cfg.keyword.border_subgroup or border == cfg.keyword.border_none then

		tbl

			:addClass(cfg.class.navbox_subgroup)

			:cssText(argscfg.arg.bodystyle])

			:cssText(argscfg.arg.style])

	else  -- regular navbox - bodystyle and style will be applied to the wrapper table

		tbl

			:addClass(cfg.class.navbox_inner)

			:css('background', 'transparent')

			:css('color', 'inherit')

	end

	tbl:cssText(argscfg.arg.innerstyle])



	renderTitleRow(tbl)

	renderAboveRow(tbl)

	local listnums_size = #listnums

	for i, listnum in ipairs(listnums) do

		renderListRow(tbl, i, listnum, listnums_size)

	end

	renderBelowRow(tbl)



	return tbl

end



local function add_navbox_styles(hiding_templatestyles)

	local frame = mw.getCurrentFrame()

	-- This is a lambda so that it doesn't need the frame as a parameter

	local function add_user_styles(templatestyles)

		if templatestyles and templatestyles ~= '' then

			return frame:extensionTag{

				name = 'templatestyles', args = { src = templatestyles }

			}

		end

		return ''

	end



	-- get templatestyles. load base from config so that Lua only needs to do

	-- the work once of parser tag expansion

	local base_templatestyles = cfg.templatestyles

	local templatestyles = add_user_styles(argscfg.arg.templatestyles])

	local child_templatestyles = add_user_styles(argscfg.arg.child_templatestyles])



	-- The 'navbox-styles' div exists to wrap the styles to work around T200206

	-- more elegantly. Instead of combinatorial rules, this ends up being linear

	-- number of CSS rules.

	return mw.html.create('div')

		:addClass(cfg.class.navbox_styles)

		:wikitext(

			add_list_styles() .. -- see [hlist_note] applied to 'before base_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

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

		argsk = gsub(arg, strip_marker_pattern, '')

	end

	return templatestyles_markers

end



function p._navbox(navboxArgs)

	args = navboxArgs

	local hiding_templatestyles = move_hiding_templatestyles(args)

	local listnums = {}



	for k, _ in pairs(args) do

		if type(k) == 'string' then

			local listnum = k:match(cfg.pattern.listnum)

			if listnum then table.insert(listnums, tonumber(listnum)) end

		end

	end

	table.sort(listnums)



	local border = mw.text.trim(argscfg.arg.border or args1 or '')

	if border == cfg.keyword.border_child then

		border = cfg.keyword.border_subgroup

	end



	-- render the main body of the navbox

	local tbl = renderMainTable(border, listnums)



	local res = mw.html.create()

	-- render the appropriate wrapper for the navbox, based on the border param



	if border == cfg.keyword.border_none then

		res:node(add_navbox_styles(hiding_templatestyles))

		local nav = res:tag('div')

			:attr('role', 'navigation')

			:node(tbl)

		-- aria-labelledby title, otherwise above, otherwise lone group

		if argscfg.arg.title or argscfg.arg.above or (argscfg.arg.group1

			and not argscfg.arg.group2]) then

			nav:attr(

				'aria-labelledby',

				mw.uri.anchorEncode(

					argscfg.arg.title or argscfg.arg.above or argscfg.arg.group1

				)

			)

		else

			nav:attr('aria-label', cfg.aria_label)

		end

	elseif border == cfg.keyword.border_subgroup then

		-- We assume that this navbox is being rendered in a list cell of a

		-- parent navbox, and is therefore inside a div with padding:0em 0.25em.

		-- We start with a </div> to avoid the padding being applied, and at the

		-- end add a <div> to balance out the parent's </div>

		res

			:wikitext('</div>')

			:node(tbl)

			:wikitext('<div>')

	else

		res:node(add_navbox_styles(hiding_templatestyles))

		local nav = res:tag('div')

			:attr('role', 'navigation')

			:addClass(cfg.class.navbox)

			:addClass(argscfg.arg.navboxclass])

			:cssText(argscfg.arg.bodystyle])

			:cssText(argscfg.arg.style])

			:css('padding', '3px')

			:node(tbl)

		-- aria-labelledby title, otherwise above, otherwise lone group

		if argscfg.arg.title or argscfg.arg.above

			or (argscfg.arg.group1 and not argscfg.arg.group2]) then

			nav:attr(

				'aria-labelledby',

				mw.uri.anchorEncode(argscfg.arg.title or argscfg.arg.above or argscfg.arg.group1])

			)

		else

			nav:attr('aria-label', cfg.aria_label)

		end

	end



	if (argscfg.arg.nocat or cfg.keyword.nocat_false):lower() == cfg.keyword.nocat_false then

		renderTrackingCategories(res, border)

	end

	return striped(tostring(res), border)

end



function p.navbox(frame)

	if not getArgs then

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

	end

	args = getArgs(frame, {wrappers = {cfg.pattern.navbox}})



	-- Read the arguments in the order they'll be output in, to make references

	-- number in the right order.

	local _

	_ = argscfg.arg.title

	_ = argscfg.arg.above

	-- Limit this to 20 as covering 'most' cases (that's a SWAG) and because

	-- iterator approach won't work here

	for i = 1, 20 do

		_ = argsformat(cfg.arg.group_and_num, i)]

		_ = argsformat(cfg.arg.list_and_num, i)]

	end

	_ = argscfg.arg.below



	return p._navbox(args)

end



return p

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook