Module:InfoboxLite

From Dune: Awakening Community Wiki
Jump to navigation Jump to search

Module:InfoboxLite

A lightweight variant of Module:Infobox for infoboxes that don't need the full feature set. Identical interface but with the following removed:

  • TemplateStyles loading
  • child / subbox support
  • autoheaders
  • Navbar
  • Italic title

These were removed because there are currently no use cases for them on the wiki, and calling them 150~ times on content-heavy pages (such as Research) was causing Lua CPU timeouts.

Row limit

Numbered parameters (data, header, label, image, subheader) are capped at 10. If a parameter beyond this limit is detected (e.g. |data11=), a visible error row is rendered inside the infobox. Use Module:Infobox directly if you genuinely need more than 10 rows.

Usage

Use Template:InfoboxLite in place of Template:Infobox. All parameters are identical.


local p = {}

local MAX_ROWS = 10

local category_in_empty_row_pattern = '%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*]]'

local function fixChildBoxes(sval, tt)
	local function notempty(s) return s and s:match('%S') end

	if notempty(sval) then
		local marker = '<span class=special_infobox_marker>'
		local s = sval

		-- Move templatestyles and categories inside table rows
		local slast = ''
		while slast ~= s do
			slast = s
			s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(%[%[%s*[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]%s*:[^]]*%]%])', '%2%1')
			s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>%s*)(\127[^\127]*UNIQ%-%-templatestyles%-%x+%-QINU[^\127]*\127)', '%2%1')
		end

		s = mw.ustring.gsub(s, '(<%s*[Tt][Rr])', marker .. '%1')
		s = mw.ustring.gsub(s, '(</[Tt][Rr]%s*>)', '%1' .. marker)

		if s:match(marker) then
			s = mw.ustring.gsub(s, marker .. '%s*' .. marker, '')
			s = mw.ustring.gsub(s, '([\r\n]|-[^\r\n]*[\r\n])%s*' .. marker, '%1')
			s = mw.ustring.gsub(s, marker .. '%s*([\r\n]|-)', '%1')
			s = mw.ustring.gsub(s, '(</[Cc][Aa][Pp][Tt][Ii][Oo][Nn]%s*>%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, '(<%s*[Tt][Aa][Bb][Ll][Ee][^<>]*>%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, '^(%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, '([\r\n]%{|[^\r\n]*[\r\n]%s*)' .. marker, '%1')
			s = mw.ustring.gsub(s, marker .. '(%s*</[Tt][Aa][Bb][Ll][Ee]%s*>)', '%1')
			s = mw.ustring.gsub(s, marker .. '(%s*\n|%})', '%1')
		end

		if s:match(marker) then
			local subcells = mw.text.split(s, marker)
			s = ''
			for k = 1, #subcells do
				if k == 1 then
					s = s .. subcells[k] .. '</' .. tt .. '></tr>'
				elseif k == #subcells then
					local rowstyle = ' style="display:none"'
					if notempty(subcells[k]) then rowstyle = '' end
					s = s .. '<tr' .. rowstyle .. '><' .. tt .. ' colspan=2>\n' .. subcells[k]
				elseif notempty(subcells[k]) then
					if (k % 2) == 0 then
						s = s .. subcells[k]
					else
						s = s .. '<tr><' .. tt .. ' colspan=2>\n' ..
							subcells[k] .. '</' .. tt .. '></tr>'
					end
				end
			end
		end

		-- Newline fix for PHP parser (phab:T191516)
		s = mw.ustring.gsub(s, '([\r\n][%*#;:][^\r\n]*)$', '%1\n')
		s = mw.ustring.gsub(s, '^([%*#;:][^\r\n]*)$', '%1\n')
		s = mw.ustring.gsub(s, '^([%*#;:])', '\n%1')
		s = mw.ustring.gsub(s, '^(%{%|)', '\n%1')
		return s
	else
		return sval
	end
end

-- ---------------------------------------------------------------------------
-- Render helpers
-- ---------------------------------------------------------------------------

local function renderErrorRow(root, message)
	root
		:tag('tr')
			:tag('td')
				:attr('colspan', '2')
				:addClass('infobox-data')
				:cssText('color: red; font-weight: bold;')
				:wikitext('<span class="error">⚠ InfoboxLite error: ' .. message .. '</span>')
end

local function renderAboveRow(root, args)
	if not args.above then return end
	root
		:tag('tr')
			:tag('th')
				:attr('colspan', '2')
				:addClass('infobox-above')
				:addClass(args.aboveclass)
				:cssText(args.abovestyle)
				:wikitext(fixChildBoxes(args.above, 'th'))
end

local function renderSubheaders(root, args)
	-- Alias bare |subheader= to |subheader1=
	if args.subheader and not args.subheader1 then
		args.subheader1 = args.subheader
	end
	for i = 1, MAX_ROWS do
		local val = args['subheader' .. i]
		if val and val:gsub(category_in_empty_row_pattern, ''):match('^%S') then
			root
				:tag('tr')
					:tag('td')
						:attr('colspan', '2')
						:addClass('infobox-subheader')
						:addClass(args.subheaderclass)
						:cssText(args.subheaderstyle)
						:cssText(args['subheaderstyle' .. i])
						:wikitext(fixChildBoxes(val, 'td'))
		end
	end
	-- Visible error if subheader11+ detected
	if args['subheader' .. (MAX_ROWS + 1)] then
		renderErrorRow(root, '|subheader' .. (MAX_ROWS + 1) .. '= exceeds the maximum of ' .. MAX_ROWS .. ' subheaders.')
	end
end

local function renderImages(root, args)
	if args.image and not args.image1 then
		args.image1 = args.image
	end
	if args.caption and not args.caption1 then
		args.caption1 = args.caption
	end
	for i = 1, MAX_ROWS do
		local img = args['image' .. i]
		if img and img:gsub(category_in_empty_row_pattern, ''):match('^%S') then
			local data = mw.html.create()
			data:wikitext(img)
			local caption = args['caption' .. i]
			if caption then
				data:tag('div')
					:addClass('infobox-caption')
					:cssText(args.captionstyle)
					:wikitext(caption)
			end
			root
				:tag('tr')
					:addClass(args['imagerowclass' .. i])
					:tag('td')
						:attr('colspan', '2')
						:addClass('infobox-image')
						:addClass(args.imageclass)
						:cssText(args.imagestyle)
						:wikitext(fixChildBoxes(tostring(data), 'td'))
		end
	end
	-- Visible error if image11+ detected
	if args['image' .. (MAX_ROWS + 1)] then
		renderErrorRow(root, '|image' .. (MAX_ROWS + 1) .. '= exceeds the maximum of ' .. MAX_ROWS .. ' images.')
	end
end

local function renderRows(root, args, empty_row_categories)
	for i = 1, MAX_ROWS do
		local header = args['header' .. i]
		local data   = args['data' .. i]
		local label  = args['label' .. i]

		if header and header ~= '_BLANK_' then
			root
				:tag('tr')
					:addClass(args['rowclass' .. i])
					:cssText(args['rowstyle' .. i])
					:tag('th')
						:attr('colspan', '2')
						:addClass('infobox-header')
						:addClass(args.headerclass)
						:cssText(args.headerstyle)
						:cssText(args['rowcellstyle' .. i])
						:wikitext(fixChildBoxes(header, 'th'))
		elseif data and data:gsub(category_in_empty_row_pattern, ''):match('^%S') then
			local row = root:tag('tr')
			row:addClass(args['rowclass' .. i])
			row:cssText(args['rowstyle' .. i])

			if label then
				row
					:tag('th')
						:attr('scope', 'row')
						:addClass('infobox-label')
						:cssText(args.labelstyle)
						:cssText(args['rowcellstyle' .. i])
						:wikitext(label)
						:done()
			end

			local dataCell = row:tag('td')
			dataCell
				:attr('colspan', not label and '2' or nil)
				:addClass(not label and 'infobox-full-data' or 'infobox-data')
				:addClass(args['class' .. i])
				:cssText(args.datastyle)
				:cssText(args['rowcellstyle' .. i])
				:wikitext(fixChildBoxes(data, 'td'))
		else
			table.insert(empty_row_categories, data or '')
		end
	end

	-- Visible error if row 11+ detected
	if args['data' .. (MAX_ROWS + 1)] or args['header' .. (MAX_ROWS + 1)] then
		renderErrorRow(root, '|data/' .. 'header' .. (MAX_ROWS + 1) .. '= exceeds the maximum of ' .. MAX_ROWS .. ' rows.')
	end
end

local function renderBelowRow(root, args)
	if not args.below then return end
	root
		:tag('tr')
			:tag('td')
				:attr('colspan', '2')
				:addClass('infobox-below')
				:addClass(args.belowclass)
				:cssText(args.belowstyle)
				:wikitext(fixChildBoxes(args.below, 'td'))
end

local function renderEmptyRowCategories(root, empty_row_categories)
	for _, s in ipairs(empty_row_categories) do
		root:wikitext(s)
	end
end

-- ---------------------------------------------------------------------------
-- Main entry point
-- ---------------------------------------------------------------------------

local function _infobox(origArgs)
	-- Normalise: treat blank args as nil, consistent with ParserFunctions
	local args = {}
	for k, v in pairs(origArgs) do
		v = mw.text.trim(v)
		if v ~= '' then args[k] = v end
	end

	local empty_row_categories = {}

	local root = mw.html.create('table')
	root
		:addClass('infobox')
		:addClass(args.bodyclass)
		:cssText(args.bodystyle)

	if args.title then
		root
			:tag('caption')
				:addClass('infobox-title')
				:addClass(args.titleclass)
				:cssText(args.titlestyle)
				:wikitext(args.title)
	end

	renderAboveRow(root, args)
	renderSubheaders(root, args)
	renderImages(root, args)
	renderRows(root, args, empty_row_categories)
	renderBelowRow(root, args)
	renderEmptyRowCategories(root, empty_row_categories)

	return tostring(root)
end

function p.infobox(frame)
	local origArgs
	if frame == mw.getCurrentFrame() then
		origArgs = frame:getParent().args
	else
		origArgs = frame
	end
	return _infobox(origArgs)
end

-- For calling via #invoke within a template (args passed directly, not via parent)
function p.infoboxTemplate(frame)
	return _infobox(frame.args)
end

return p