Module:Simple navbox/sandbox

From Uncyclopedia, the content-free encyclopedia
Jump to navigation Jump to search
local p = {}
local getArgs = require('Module:Arguments').getArgs

--------------------------------------------------------------------------------
-- Utilities
--------------------------------------------------------------------------------

local function lc(v)
	return type(v) == 'string' and mw.ustring.lower(v) or v
end

-- Check whether a style string sets a background property
local function hasBackground(s)
	-- 'background' is a substring of 'background-color', so one check suffices
	return type(s) == 'string' and s:find('background', 1, true)
end

-- Check whether a style string sets the font color property
local function hasColor(s)
	if type(s) ~= 'string' then return false end
	s = s:lower()
	return s:match('^%s*color%s*:') or s:match('[;%s]color%s*:')
end

-- Returns true if the string contains constructs requiring parser expansion
local function needsPreprocess(s)
	if type(s) ~= 'string' or s == '' then return false end
	return s:find('{{', 1, true)
		or s:find('{|', 1, true)
		or s:find('<', 1, true)
end

-- Prepend basestyle to another style string, joining with semicolons
local function prependBaseStyle(a, b)
	a, b = a or '', b or ''
	if a ~= '' and b ~= '' then return a .. ';' .. b end
	return a .. b
end

--------------------------------------------------------------------------------
-- Batch preprocess: runs frame:preprocess on a list of strings in one call
-- by joining with a unique separator, then splitting the result back apart.
-- This avoids per-item preprocess overhead on MediaWiki.
--------------------------------------------------------------------------------

local function batchPreprocess(frame, items)
	if #items == 0 then return {} end
	if #items == 1 then return { frame:preprocess(items[1]) } end

	local sep = '@@SIMPLE_SEPARATOR@@'
	local processed = frame:preprocess(table.concat(items, sep))
	local results = {}

	for part in mw.ustring.gmatch(processed, '(.-)' .. sep) do
		table.insert(results, part)
	end
	-- The last segment isn't captured by the pattern above
	if #results < #items then
		table.insert(
			results,
			mw.ustring.match(processed, '.*' .. sep .. '(.*)') or items[#items]
		)
	end

	return results
end

--------------------------------------------------------------------------------
-- Selective preprocess: only sends items through frame:preprocess if they
-- contain wiki markup (templates, tables, HTML tags). Plain-text items are
-- left as-is, avoiding unnecessary parser calls.
--------------------------------------------------------------------------------

local function selectivePreprocess(frame, items)
	local toPreprocess = {}
	local preprocessIndices = {}

	for idx, val in ipairs(items) do
		if needsPreprocess(val) then
			table.insert(toPreprocess, val)
			table.insert(preprocessIndices, idx)
		end
	end

	local processed = batchPreprocess(frame, toPreprocess)
	local results = {}

	for idx, val in ipairs(items) do
		results[idx] = val
	end
	for j, pos in ipairs(preprocessIndices) do
		results[pos] = processed[j] or ''
	end

	return results
end

--------------------------------------------------------------------------------
-- Building blocks for the navbox
--------------------------------------------------------------------------------

local function buildHeader(frame, outerDiv, args, basestyle)
	local headerstyle = prependBaseStyle(basestyle, args.headerstyle or args.titlestyle or '')
	local headerClass = 'simple-header NavHead'
	if headerstyle ~= '' and hasColor(headerstyle) then
		headerClass = headerClass .. ' simple-header-modified'
	end

	local headerContent = {}
	if args.name then
		table.insert(headerContent, frame:expandTemplate{ title = 'simple navbar', args = { args.name } })
	end
	table.insert(headerContent, args.title or 'Default title: add a value for <code>title</code> in the template.')

	outerDiv:tag('div')
		:addClass(headerClass)
		:cssText(headerstyle)
		:wikitext(table.concat(headerContent))
end

local function buildGroupedContent(frame, contentDiv, args, basestyle)
	local groupstyleBase = prependBaseStyle(basestyle, args.groupstyle or '')
	local oddGStyle  = groupstyleBase .. (args.oddgroupstyle  and ';' .. args.oddgroupstyle  or '')
	local evenGStyle = groupstyleBase .. (args.evengroupstyle and ';' .. args.evengroupstyle or '')
	local oddLStyle  = (args.liststyle or '') .. (args.oddliststyle  and ';' .. args.oddliststyle  or '')
	local evenLStyle = (args.liststyle or '') .. (args.evenliststyle and ';' .. args.evenliststyle or '')

	local tableClass = 'simple-group-container'
	if hasBackground(args.groupstyle) or hasBackground(args.oddgroupstyle) or hasBackground(args.evengroupstyle) then
		tableClass = tableClass .. ' simple-group-modified'
	end
	if hasBackground(args.liststyle) or hasBackground(args.oddliststyle) or hasBackground(args.evenliststyle) then
		tableClass = tableClass .. ' simple-list-modified'
	end
	if hasColor(oddGStyle) or hasColor(evenGStyle) then
		tableClass = tableClass .. ' simple-group-inherit'
	end

	local groupTable = contentDiv:tag('table')
		:addClass(tableClass)
		:cssText(args.tablestyle or '')

	-- Collect group indices and raw list values
	local groupIndices = {}
	local rawLists = {}

	for i = 1, 50 do
		if args['group' .. i] then
			table.insert(groupIndices, i)
			rawLists[#groupIndices] = args['list' .. i] or ''
		end
	end

	-- Selectively preprocess only lists that contain wiki markup
	local preprocessedLists = selectivePreprocess(frame, rawLists)

	-- Build rows
	for idx, i in ipairs(groupIndices) do
		local group = args['group' .. i]
		local listContent = preprocessedLists[idx] or ''
		local odd = (i % 2 == 1)
		local gstyle = (odd and oddGStyle or evenGStyle) .. ';' .. (args['group' .. i .. 'style'] or '')
		local lstyle = (odd and oddLStyle or evenLStyle) .. ';' .. (args['list' .. i .. 'style'] or '')

		local listClass = 'simple-list'
		if listContent:find('simple%-child', 1, false) then
			listClass = listClass .. ' simple-has-child'
		end

		local row = groupTable:tag('tr')
			:addClass('simple-group-row')
			:cssText(args.rowstyle or '')

		row:tag('td')
			:addClass('simple-group')
			:cssText(gstyle)
			:wikitext(group)

		row:tag('td')
			:addClass(listClass)
			:cssText(lstyle)
			:wikitext('\n' .. listContent .. '\n')
	end
end

local function buildUngroupedContent(frame, contentDiv, args)
	local list = args.list or args.list1 or 'Error: <code>list</code> is empty!'
	if list ~= '' and needsPreprocess(list) then
		list = frame:preprocess(list)
	end
	contentDiv:tag('div')
		:addClass('simple-list simple-list-only')
		:cssText(args.liststyle or '')
		:wikitext('\n' .. list .. '\n')
end

local function buildImages(container, args, hasOuterImages)
	local imageValign = args['image-valign'] or args.imagevalign or 'center'

	-- Left inner image
	local imageLeft = args.image2 or args.imageleft
	if not hasOuterImages and imageLeft then
		container:tag('div')
			:addClass('simple-image')
			:css('align-self', imageValign)
			:wikitext(imageLeft)
	end

	-- Right inner image (added after content, so call separately)
	return imageValign
end

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

function p.main(frame)
	local args = getArgs(frame, { removeBlanks = false })

	-- Stylesheets
	local stylesheets = {}
	table.insert(stylesheets, frame:extensionTag{ name = 'templatestyles', args = { src = 'Simple navbox/styles.css' } })
	table.insert(stylesheets, frame:extensionTag{ name = 'templatestyles', args = { src = 'Hlist/styles.css' } })
	if args.templatestyles then
		table.insert(stylesheets, frame:extensionTag{ name = 'templatestyles', args = { src = args.templatestyles } })
	end

	local basestyle = args.basestyle or ''

	-- State / collapse logic
	local state = lc(args.collapse or args.collapsible or args.state or '')
	if state == 'no' then state = 'plain' end

	local collapsible = state ~= 'plain'
	local allowOuterImages = not collapsible
	local child = lc(args.child) or ''

	-- Outer image handling
	local userSetOuterImage = args['image-outer'] or args.imageouter or args.imageouterleft
		or args['image-outer-right'] or args.imageouterright or args.imageouter2
	if userSetOuterImage and collapsible then
		mw.addWarning("<code>imageouter</code> type images will not be shown unless <code>|state = plain</code> or <code>|collapse = no</code>.")
	end

	local imageOuterLeft  = allowOuterImages and (args['image-outer'] or args.imageouter or args.imageouterleft)
	local imageOuterRight = allowOuterImages and (args['image-outer-right'] or args.imageouterright or args.imageouter2)
	local hasOuterImages  = imageOuterLeft or imageOuterRight

	-- Warn if both inner and outer images are set
	local hasInnerImages = args.image or args.image2 or args.imageleft
	if hasOuterImages and hasInnerImages then
		mw.addWarning("When <code>|state = plain</code> or <code>|collapse = no</code>, <code>image</code> ''or'' <code>imageouter</code> type images can be used, but ''not'' both.")
	end

	-- Assemble CSS classes for the outer div
	local classes = { 'simple-navbox', args.bodyclass or args.class or '', 'noprint' }

	if child == 'yes' then
		table.insert(classes, 'simple-child')
	end

	if collapsible and child ~= 'yes' then
		table.insert(classes, 'NavFrame')
		if state == 'collapsed' or state == 'closed' then
			table.insert(classes, 'NavClosed')
		elseif state == 'expanded' or state == 'uncollapsed' or state == 'open' then
			table.insert(classes, 'NavOpen')
		end
	elseif hasOuterImages then
		table.insert(classes, 'simple-outer')
	end

	if basestyle ~= '' then
		table.insert(classes, 'simple-basestyle')
	end

	if args.group1 and (hasBackground(args.groupstyle) or hasBackground(args.oddgroupstyle) or hasBackground(args.evengroupstyle)) then
		table.insert(classes, 'simple-has-modified-groups')
	end

	-- Build outer div
	local outerDiv = mw.html.create('div')
		:addClass(table.concat(classes, ' '))
		:cssText(args.bodystyle or '')

	-- Outer left image
	if imageOuterLeft then
		outerDiv:tag('div')
			:addClass('simple-outer-image')
			:wikitext(imageOuterLeft)
	end

	-- Header
	buildHeader(frame, outerDiv, args, basestyle)

	-- Nav content wrapper
	local navContent = outerDiv:tag('div')
		:addClass('simple-navcontent NavContent')
		:cssText(args.navcontentstyle or '')

	-- Above block
	if args.above then
		local aboveVal = args.above
		if needsPreprocess(aboveVal) then
			aboveVal = frame:preprocess(aboveVal)
		end
		navContent:tag('div')
			:addClass('simple-group-above')
			:cssText(prependBaseStyle(basestyle, args.abovestyle or ''))
			:wikitext('\n' .. aboveVal .. '\n')
	end

	-- Flex container for content + images
	local imageAlign = lc(args['image-align'] or args.imagealign or '')
	local flex = 'display:flex; flex-flow:row;'
	if imageAlign == 'left' then
		flex = 'display:flex; flex-flow:row-reverse; justify-content:flex-end;'
	end

	local container = navContent:tag('div')
		:addClass('simple-content-container')
		:cssText(flex .. ' ' .. (args.containerstyle or ''))

	-- Left inner image
	local imageValign = buildImages(container, args, hasOuterImages)

	-- Content area
	local contentDiv = container:tag('div')
		:addClass('simple-content')
		:addClass(args.listclass or '')
		:cssText(args.contentstyle or '')

	if args.group1 then
		buildGroupedContent(frame, contentDiv, args, basestyle)
	else
		buildUngroupedContent(frame, contentDiv, args)
	end

	-- Right inner image
	if not hasOuterImages and args.image then
		container:tag('div')
			:addClass('simple-image')
			:css('align-self', imageValign)
			:wikitext(args.image)
	end

	-- Below block
	if args.below then
		local belowVal = args.below
		if needsPreprocess(belowVal) then
			belowVal = frame:preprocess(belowVal)
		end
		navContent:tag('div')
			:addClass('simple-group-above simple-group-below')
			:cssText(prependBaseStyle(basestyle, args.belowstyle or args.abovestyle or ''))
			:wikitext('\n' .. belowVal .. '\n')
	end

	-- Outer right image
	if imageOuterRight then
		outerDiv:tag('div')
			:addClass('simple-outer-imageright')
			:wikitext(imageOuterRight)
	end

	return table.concat(stylesheets) .. tostring(outerDiv)
end

return p