×
Create a new article
Write your page title here:
We currently have 179 articles on NBITTRPG Wiki. Type your article name above or click on one of the titles below and start writing!



NBITTRPG Wiki

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

--------------------------------------------------------------------
--<pre> Navbox Module
--
-- * Fully CSS styled (inline styles possible but not default)
-- * Supports unlimited rows
-- * Automatic striping alternation, including for child navbox subsections
--
-- Original module by User:Tjcool007 from layton.fandom.com
--------------------------------------------------------------------

local p = {}

local i18n = require('Module:I18n').loadMessages('Navbox')

local showText, hideText = i18n:msg('show'), i18n:msg('hide')

local cfg = {
	marker = {
		oddeven = '\127_ODDEVEN_\127',
		contains_subgroup = '\127_ODDEVEN0_\127',
		restart_header = '\127_ODDEVEN1_\127',
		regex_changer = '\127_ODDEVEN([01]?)_\127',
		odd = '\127_ODD_\127',
		even = '\127_EVEN_\127',
		oddeven_sep = '\127_ODDEVEN2_\127',
		oddeven_end = '\127_ODDEVEN3_\127',
		regex_oddeven_sep = '\127_ODDEVEN[23]_\127',
		regex_odd = '(\127_ODD_\127[^\127]*)\127_ODDEVEN2_\127([^\127]*)\127_ODDEVEN2_\127[^\127]*\127_ODDEVEN3_\127',
		regex_even = '(\127_EVEN_\127[^\127]*)\127_ODDEVEN2_\127[^\127]*\127_ODDEVEN2_\127([^\127]*)\127_ODDEVEN3_\127'
	},
	pattern = {
		hlist = 'hlist',
		plainlist = 'plainlist',
	},

	category = {
		error = '[[Category:Navboxes with internal errors]]',
		orphan = '[[Category:Navbox orphans]]'
	}
}

------------------------------------------------
-- ARGUMENTS PREPROCESSOR
-- * Extracts arguments from frame and stores them in args table
-- * At the same time, checks for valid row numbers
------------------------------------------------

--- Preprocessor for the arguments.
-- Will fill up the args table with the parameters from the frame grouped by their type.
--
-- @param frame The frame passed to the Module.
local function preProcessArgs(frame)
	local tmp, args, rownums, skiprows = {}, {}, {}, {}

	if frame == mw.getCurrentFrame() then
		tmp = frame:getParent().args
	else
		tmp = frame
	end

	-- Storage tables
	local nums, subnums = {}, {}

	-- Loop over all the args
	for k, v in pairs(tmp) do
		-- Skip empty args, which are useless
		if v ~= '' then
			local str = tostring(k)
			local cat, num, subnum = str:match('^(%a+)([1-9]%d*)%.([1-9][%d%.]*)$')
			if subnum then
				nums[num] = true
				if type(subnums[num]) ~= "table" then
					subnums[num] = {}
				end
				subnums[num][cat..subnum] = v
			else
				cat, num = str:match('^(%a+)([1-9]%d*)$')
				if cat == 'header' or cat == 'list' then
					nums[num] = true
				end
				args[k] = v -- Simple copy
			end
		end
	end

	-- Generate child navboxes
	for k, v in pairs(subnums) do
		for _, arg in ipairs({'showall', 'alternaterows', 'titlestyle', 'titleclass', 'abovestyle', 'aboveclass', 'rowclass', 'altrowclass', 'groupstyle', 'groupclass', 'altgroupstyle', 'altgroupclass', 'liststyle', 'listclass', 'altliststyle', 'altlistclass', 'belowstyle', 'belowclass', 'defaultstate', 'defaultcollapsetext', 'defaultexpandtext'}) do
			v[arg] = args[arg]
		end
		v.border = 'child'
		args['list'..k] = p.main(v)
	end

	for k, v in pairs(nums) do
		rownums[#rownums+1] = tonumber(k)
	end

	table.sort(rownums)

	-- Calculate skip rows
	local cSection, cSkip
	local showall = args.showall
	for i=1,#rownums do
		local num = rownums[i]
		if args['header'..num] then
			cSection = true
			cSkip = false
			local showme = args['show'..num]
			if showme == 'no' then
				cSkip = true
			elseif showme == 'auto' or (showme ~= 'yes' and showall ~= 'yes') then
				if not args['list'..num] then
					local nextNum = rownums[i+1]
					cSkip = not nextNum or args['header'..nextNum] -- If next has a header -> skip
				end
			end
		end
		if cSection and cSkip then
			skiprows[num] = true
		end
	end

	-- Insert markers into class and style parameters where needed
	for i, v in ipairs({'groupclass', 'groupstyle', 'listclass', 'liststyle'}) do
		if args['alt' .. v] then
			args[v] = cfg.marker.oddeven_sep .. (args[v] or '') .. cfg.marker.oddeven_sep .. args['alt' .. v] .. cfg.marker.oddeven_end
		end
	end

	return args, rownums, skiprows
end

------------------------------------------------
-- MAIN FUNCTIONS
------------------------------------------------

--- Processes the arguments to create the navbox.
--
-- @return A string with HTML that is the navbox.
local function _navbox(args, rownums, skiprows)
	local navbox -- Actual navbox

	local hasrows, hasData, hasTitle, hasChild = false, false, false, false
	local activeSection, sections, cimage, cimageleft
	
	local border = args.border or mw.text.trim(args[1] or '') or ''
	if border == 'child' then border = 'subgroup' end
	local isChild = (border == 'subgroup')

	local colspan = args.image and 3 or 2
	if args.imageleft then colspan = colspan + 1 end
	local rowspan = 0

	--- Wraps text in newlines if it begins with a list or table
	local function processItem(item)
		if item:sub(1, 2) == '{|' then
			return '\n' .. item ..'\n'
		end
		if item:match('^[*:;#]') then
			return '\n' .. item ..'\n'
		end
		return item
	end

	--- Returns navbox contents with markers replaced for odd/even striping
	local function striped(wikitext, border)
		-- 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 == 'subgroup' and args.orphan ~= 'yes' then
			-- No change; striping occurs in outermost navbox.
			return wikitext .. orphanCat
		end
		local altrowclass = args.altrowclass or 'alt'
		altrowclass = ' ' .. altrowclass
		local first, second = '', altrowclass
		if args.alternaterows then
			if args.alternaterows == 'swap' then
				first, second = second, first
			elseif args.alternaterows == 'no' or args.alternaterows == 'odd' then
				second = ''
			elseif args.alternaterows == 'even' then
				first = altrowclass
			else
				first = args.alternaterows
				second = first
			end
		end
		local changer
		if first == second then
			changer = cfg.marker.odd
		else
			local index = 0
			changer = function (code)
				if code == '0' then
					-- Subgroup encountered.
					-- Return a marker without incrementing the index.
					return index % 2 == 0 and cfg.marker.odd or cfg.marker.even
				elseif code == '1' then
					-- Title or header encountered.
					-- Restart the index and remove the marker.
					-- The next occurrence will use the initial class.
					index = 0
					return ''
				end
				index = index + 1
				return index % 2 == 1 and cfg.marker.odd or cfg.marker.even
			end
		end
		local regex = orphanCat:gsub('([%[%]])', '%%%1')
		wikitext = wikitext:gsub(regex, ''):gsub(cfg.marker.regex_changer, changer)
		local prevWikitext, hasNoMoreOddevenMarkers
		repeat
			prevWikitext = wikitext
			wikitext = wikitext:gsub(cfg.marker.regex_odd, '%1%2'):gsub(cfg.marker.regex_even, '%1%2')
			hasNoMoreOddevenMarkers = wikitext:match(cfg.marker.regex_oddeven_sep) == nil
		until hasNoMoreOddevenMarkers or wikitext == prevWikitext
		wikitext = wikitext:gsub(cfg.marker.odd, first):gsub(cfg.marker.even, second)
		if not hasNoMoreOddevenMarkers then
			-- Something went wrong. Flag for investigation.
			-- See <https://discord.com/channels/246075715714416641/1266361437484486778> for details.
			wikitext = wikitext .. cfg.category.error
		end
		return wikitext
	end

	------------------------------------------------
	-- Title
	------------------------------------------------

	--- Processes the VDE links in the title
	--
	-- @param titlecell The table cell of the title
	local function processVde( titlecell )
		if not args.template then return end

		titlecell:wikitext('<span class="navbox-vde">'
			.. mw.getCurrentFrame():expandTemplate({
				title = 'vdelinks',
				args = { args.template, ['type'] = 'navbox' }
			}) .. '</span>')
	end

	--- Processes the main title row
	local function processTitle()
		if not hasTitle then return end

		local titlerow = mw.html.create('tr'):addClass('navbox-title'..cfg.marker.restart_header)
		local titlecell = mw.html.create('th'):attr('colspan',colspan):attr('scope','col')
		local titlediv = mw.html.create('div')

		if not pcall( processVde, titlecell ) then
			titlediv:wikitext( '<b class="navbox-vde error" title="Missing Template:Vdelinks">!!!</b>' )
		end

		titlediv:wikitext( args.title or '{{{title}}}' )

		-- Padding
		local hasTemplate = args.template ~= nil
		local hasState = not args.state or args.state ~= 'plain'

		if hasTemplate or hasState then
			-- remove redundant classes in future if it won't break the display of wikis' navboxes
			titlediv:addClass('navbox-title-pad navbox-title-padleft navbox-title-padright')
		end

		if args.titleclass then titlerow:addClass( args.titleclass ) end
		if args.titlestyle then titlecell:cssText( args.titlestyle ) end

		titlecell:node(titlediv)
		titlerow:node(titlecell)
		navbox:node(titlerow)
	end

	local function _addGutter( parent, incRowspan )
		parent:tag('tr'):addClass('navbox-gutter'):tag('td'):attr('colspan',2)

		if incRowspan then
			rowspan = rowspan + 1
		end
	end

	------------------------------------------------
	-- Above/Below
	------------------------------------------------

	--- Processes the above and below rows
	--
	-- @param rowtype Either 'above' or 'below'
	local function processAboveBelow( rowtype )
		if not args[rowtype] then return end

		local abrow = mw.html.create('tr'):addClass('navbox-'..rowtype)
		local abcell = mw.html.create('td'):attr('colspan',colspan):wikitext( processItem(args[rowtype]) )

		if args[rowtype .. 'class'] then abrow:addClass( args[rowtype .. 'class'] ) end
		if args[rowtype .. 'style'] then abcell:cssText( args[rowtype .. 'style'] ) end

		abrow:node( abcell )
		if hasTitle or rowtype == 'below' then
			_addGutter( navbox )
		end
		navbox:node( abrow )
	end

	------------------------------------------------
	-- Main Rows
	------------------------------------------------

	--- Processes the images
	local function _processImage(row, imgtype)
		if not args[imgtype] then return end

		local iclass = imgtype == 'image' and 'navbox-image-right' or 'navbox-image-left'

		local imagecell = mw.html.create('td'):addClass('navbox-image'):addClass(iclass)

		local image = args[imgtype]
		if image:sub(1,1) ~= '[' then
			local width = args[imgtype .. 'width'] or '100px'
			imagecell:css('width',width):wikitext('['..'[' .. image  .. '|' .. width .. '|link=' .. (args[imgtype .. 'link'] or '') .. ']]')
		else
			imagecell:css('width','0%'):wikitext(image)
		end

		if args[imgtype .. 'class'] then imagecell:addClass( args[imgtype .. 'class'] ) end
		if args[imgtype .. 'style'] then imagecell:cssText( args[imgtype .. 'style'] ) end

		row:node( imagecell )
		if imgtype == 'image' then
			cimage = imagecell
		else
			cimageleft = imagecell
		end
	end

	--- Closes the currently active section (if any)
	local function _closeCurrentSection()
		if not activeSection then return end

		local row = mw.html.create('tr'):addClass('navbox-section-row')
		local cell = mw.html.create('td'):attr('colspan',2)

		if not hasrows then
			_processImage(row,'imageleft')
		end

		cell:node(sections[activeSection])
		row:node(cell)

		local firstRow = false
		if not hasrows then
			firstRow = true
			hasrows = true
			_processImage(row,'image')
		end

		if not isChild or not firstRow or hasTitle or args.above then
			_addGutter(navbox,not firstRow)
		end
		navbox:node(row)
		rowspan = rowspan + 1

		activeSection = false
		hasData = false
	end

	--- Process a single Header "row"
	--
	-- @param num Number of the row to be processed
	local function processHeader(num)
		if not args['header'..num] then return end

		_closeCurrentSection()

		local subtable = mw.html.create('table'):addClass('navbox-section')
		local headerrow = mw.html.create('tr')
		local header = mw.html.create('th'):addClass('navbox-header'..cfg.marker.restart_header):attr('colspan',2):attr('scope','col')
		local headerdiv = mw.html.create('div'):wikitext( args['header'..num] )

		local collapseme = args['state'..num] or false
		local state = false

		if collapseme then
			-- Look at this one
			if collapseme ~= 'plain' then
				state = collapseme == 'expanded' and 'expanded' or 'collapsed'
			end
		else
			-- Look at default
			local collapseall = args.defaultstate or false
			if collapseall then
				state = collapseall == 'expanded' and 'expanded' or 'collapsed'
			end
		end

		if state then
			subtable:addClass('mw-collapsible'):attr('data-expandtext',args['expandtext'..num] or args['defaultexpandtext'] or showText):attr('data-collapsetext',args['collapsetext'..num] or args['defaultcollapsetext'] or hideText)
			if state == 'collapsed' then
				subtable:addClass('mw-collapsed')
			end
			-- remove redundant classes in future if it won't break the display of wikis' navboxes
			headerdiv:addClass('navbox-header-pad navbox-title-padleft navbox-title-padright')
		end

		if args.headerclass then headerrow:addClass( args.headerclass ) end
		if args.headerstyle then header:cssText( args.headerstyle ) end

		header:node(headerdiv)
		headerrow:node(header)
		subtable:node(headerrow)

		sections[num] = subtable
		activeSection = num
	end

	-- Check if a string contains a particular CSS class
	local function hasListClass(htmlclass, arg)
		local patterns = {
			'^' .. htmlclass .. '$',
			'%s' .. htmlclass .. '$',
			'^' .. htmlclass .. '%s',
			'%s' .. htmlclass .. '%s'
		}

		for _, pattern in ipairs(patterns) do
			if mw.ustring.find(args[arg] or '', pattern) then
				return true
			end
		end
		return false
	end

	--- Processes a single list row
	--
	-- @param num Number of the row to be processed
	local function processList(num)
		if not args['list'..num] then return end

		local oddEven = cfg.marker.oddeven

		local data = args['list'..num]

		hasChild = false

		if data:sub(1, 12) == '</div><table' then
			hasChild = true
			oddEven = cfg.marker.contains_subgroup
		end

		local row = mw.html.create('tr'):addClass('navbox-row'..oddEven)

		if not hasrows and not activeSection then
			_processImage(row, 'imageleft')
		end

		local listcell = mw.html.create('td'):addClass('navbox-list')
		local hlistcell = listcell:tag('div')
		
		-- Only add hlist class if listclass parameter does not contain hlist or plainlist
		if not (args.listclass and (hasListClass(cfg.pattern.hlist, 'listclass') or hasListClass(cfg.pattern.plainlist, 'listclass'))) then
			hlistcell:addClass('hlist')
		end
		
		hlistcell:wikitext( processItem(data) )

		if args.rowclass then row:addClass( args.rowclass ) end
		if args.listclass then listcell:addClass( args.listclass ) end
		if args.liststyle then listcell:cssText( args.liststyle ) end

		if hasChild then
			listcell:cssText('background: none')
		end

		if args['group'..num] then
			local groupcell = mw.html.create('th'):addClass('navbox-group'):attr('scope','row'):wikitext( args['group'..num] )

			if args.groupclass then groupcell:addClass( args.groupclass ) end
			if args.groupstyle then groupcell:cssText( args.groupstyle ) end

			row:node( groupcell )
		else
			listcell:attr('colspan',2)
			if not hasChild then
				listcell:addClass('no-group')
			end
		end

		row:node( listcell )

		local firstRow = false
		if not hasrows and not activeSection then
			firstRow = true
			hasrows = true
			_processImage(row, 'image')
		end

		if activeSection then
			local parent = sections[activeSection]
			if not isChild or not firstRow then
				_addGutter(parent)
			end
			parent:node(row)
			hasData = true
		else
			if not isChild or not firstRow or hasTitle or args.above then
				_addGutter(navbox,not firstRow)
			end
			navbox:node( row )
			rowspan = rowspan + 1
		end
	end

	--- Processes all rows
	local function processRows()
		sections = {}
		for i=1,#rownums do
			local num = rownums[i]
			if not skiprows[num] then
				processHeader(num)
				processList(num)
			end
		end
		_closeCurrentSection()

		if cimageleft then
			cimageleft:attr('rowspan',rowspan)
		end
		if cimage then
			cimage:attr('rowspan',rowspan)
		end
	end

	-- Create the root HTML element

	if isChild then
		navbox = mw.html.create('table'):addClass('navbox-subgroup')
	else
		navbox = mw.html.create('table'):addClass('navbox')
	end

	if not isChild or args.title then
		hasTitle = true
	end

	if hasTitle and isChild then
		navbox:addClass('navbox-subgroup-with-title')
	end

	if hasTitle and args.state ~= 'plain' then
		navbox:addClass('mw-collapsible'):attr('data-expandtext',args['expandtext'] or args['defaultexpandtext'] or showText):attr('data-collapsetext',args['collapsetext'] or args['defaultcollapsetext'] or hideText)
		if args.state == 'collapsed' then
			navbox:addClass('mw-collapsed')
		end
	end

 	if args.bodyclass then navbox:addClass(args.bodyclass) end
	if args.bodystyle then navbox:cssText(args.bodystyle) end

	-- Process...
	processTitle()
	processAboveBelow('above')
	processRows()
	processAboveBelow('below')

	if not isChild then
		return striped(tostring(navbox), border)
	else
		local wrapper = mw.html.create('')
		wrapper:wikitext('</div>')
		wrapper:node(navbox)
		wrapper:wikitext('<div class="hlist">')
		return striped(tostring(wrapper), border)
	end
end

--- Main module entry point.
-- To be called with {{#invoke:navbox|main}} or directly from another module.
--
-- @param frame The frame passed to the module via the #invoke. If called from another
--              module directly, this should be a table with the parameter definition.
function p.main(frame)
	return _navbox(preProcessArgs(frame))
end

return p

Recent changes

  • Zaptrap • Friday at 22:20
  • Zaptrap • Friday at 22:18
  • Zaptrap • Friday at 22:15
  • Ember • Friday at 22:03
  • AndrewFBR • Friday at 00:24
  • AndrewFBR • Friday at 00:24
  • AndrewFBR • Friday at 00:24
  • AndrewFBR • Friday at 00:24