Toggle menu
13
222
68
27K
Kenshi Wiki
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

This module implements the {{Navbox timeline}} template. Please see the template page for usage instructions.



require('strict')

local yesno = require('Module:Yesno')
local navbox = require('Module:Navbox')._navbox
local getArgs = require('Module:Arguments').getArgs
local p = {}

-- Add blank table cells
local function addBlank(args, row, prev, current)
	if row and prev < current then
		if yesno(args.decades) == false then
			row:tag('td')
				:addClass('timeline-blank')
				:cssText(args.blankstyle)
				:attr('colspan', current - prev)
		else
			-- Divide the cell up every decade if showing decades at the top
			local year = prev
			
			while year < current do
				local dur = math.min(10 - year % 10, current - year)
				
				row:tag('td')
					:addClass('timeline-blank')
					:cssText(args.blankstyle)
					:attr('colspan', dur)
				
				year = year + dur
			end
		end
	end
end

-- Get timeline data
local function timelineInfo(args)
	local rows = {
		[1] = {},
		minYear = math.huge,
		maxYear = -math.huge,
		hasLabels = false
	}
	
	for num, fullVal in ipairs(args) do
		local key, val = fullVal:match('^([a-z]+): *(.*)$')
		local cellIndex = #rows[#rows]
		
		-- Row data key value pairs
		if cellIndex == 0 and key then
			rows[#rows][key] = val
			
			-- Record that there are labels
			if key == 'label' then
				rows.hasLabels = true
			end
		-- Data cell key value pairs
		elseif key then
			-- Data cell key value pairs
			rows[#rows][cellIndex][key] = val
		-- New row
		elseif fullVal == '' then
			if next(rows[#rows]) then
				table.insert(rows, {})
			end
		-- Add date to cell with item already and no date
		elseif cellIndex > 0
			and rows[#rows][cellIndex].item
			and not rows[#rows][cellIndex].startYear
		then
			local dates = mw.text.split(fullVal, '-', true)
			local startYear = tonumber(dates[1])
			local endYear = tonumber(dates[2]) or tonumber(os.date('%Y')) + 1
			
			if startYear == nil then
				error('Argument ' .. num .. ' is an invalid time range', 0)
			end
			
			if endYear < startYear then
				error('Argument ' .. num .. '\'s end year is less than the start year', 0)
			end

			rows[#rows][cellIndex].startYear = startYear
			rows[#rows][cellIndex].endYear = endYear

			if startYear < rows.minYear then
				rows.minYear = startYear
			end

			if endYear > rows.maxYear then
				rows.maxYear = endYear
			end
		-- New item
		else
			table.insert(rows[#rows], { item = fullVal })
		end
	end
	
	-- Add overrides from arguments
	if args.startoffset then
		rows.minYear = rows.minYear - tonumber(args.startoffset)
	end
	
	if args.startyear and tonumber(args.startyear) < rows.minYear then
		rows.minYear = tonumber(args.startyear)
	end
	
	if args.endoffset then
		rows.maxYear = rows.maxYear + tonumber(args.endoffset)
	end
	
	if args.endyear and tonumber(args.endyear) > rows.maxYear then
		rows.maxYear = tonumber(args.endyear)
	end

	return rows
end

-- Render the date rows
local function renderDates(args, tbl, rows, invert)
	local showDecades = yesno(args.decades)
	local yearRow = tbl:tag('tr')
		:addClass('timeline-row')
	
	-- Create label
	if args.label or rows.hasLabels then
		local labelCell = mw.html.create('th')
			:attr('scope', 'col')
			:addClass('navbox-group timeline-label')
			:cssText(args.labelstyle)
			:attr('rowspan', showDecades ~= false and '2' or '1')
			:wikitext(args.label or '')
			
		yearRow:node(labelCell)
	end

	-- Create decade row
	if showDecades ~= false then
		local decadeRow = tbl:tag('tr')
			:addClass('timeline-row')
		local year = rows.minYear
		
		-- Move decade row 
		if not invert then
			decadeRow, yearRow = yearRow, decadeRow
		end
		
		while year < rows.maxYear do
			local dur = math.min(10 - year % 10, rows.maxYear - year)
			
			decadeRow:tag('th')
				:attr('scope', 'col')
				:addClass('timeline-decade')
				:cssText(args.datestyle)
				:cssText(args.decadestyle)
				:attr('colspan', dur)
				:wikitext(math.floor(year / 10) .. '0s')
			
			year = year + dur
		end
	end
	
	-- Populate year row element
	local width = 100 / (rows.maxYear - rows.minYear)
	
	for i = rows.minYear, rows.maxYear - 1 do
		yearRow:tag('th')
			:attr('scope', 'col')
			:addClass('timeline-year')
			:cssText(args.datestyle)
			:cssText(args.yearstyle)
			:cssText('width:' .. width .. '%')
			:wikitext(showDecades == false and i or i % 10)
	end
end

-- Render the timeline itself
local function renderTimeline(args, tbl, rows)
	local rowElement = nil
	local rowSuffix = nil
	local prev = rows.minYear
	local prevItem = nil
	local prevLabel = nil
	local labelSpan = 0
	
	for rowNum, row in ipairs(rows) do
		local rowElement = tbl:tag('tr')
			:addClass('timeline-row')
		
		if labelSpan <= 0 and (rows.hasLabels or args.label) then
			labelSpan = tonumber(row.span) or 1
			
			prevLabel = rowElement:tag('th')
				:attr('scope', 'row')
				:attr('rowspan', labelSpan)
				:addClass('navbox-group timeline-label')
				:cssText(args.labelstyle)
				:cssText(row.labelstyle)
				:wikitext(row.label)
		end
		
		labelSpan = labelSpan - 1
		
		local prevEndYear = rows.minYear
		local prevItem = nil
		
		for cellNum, cell in ipairs(row) do
			-- Shrink previous item so new item can start at the start year
			if prevItem and prev > prevEndYear then
				prevItem:attr('colspan', prevItem:getAttr('colspan') - prev + prevEndYear);
			end

			if cell.startYear == nil then
				error('Missing timerange for row ' .. rowNum .. ' cell ' .. cellNum, 0)
			end
			
			-- Add blanks before the cell
			addBlank(args, rowElement, prevEndYear, cell.startYear)
			
			prevItem = rowElement:tag('td')
				:addClass('timeline-item')
				:cssText(args.itemstyle)
				:cssText(cell.style or '')
				:attr('colspan', cell.endYear - cell.startYear)
				:wikitext(cell.item)

			prevEndYear = cell.endYear
		end
		
		-- Add blanks to the end of the row
		addBlank(args, rowElement, prevEndYear, rows.maxYear)
	end
	
	-- Remove any extra rowspan from the label
	if prevLabel and labelSpan > 0 then
		prevLabel:attr('rowspan', prevLabel:getAttr('rowspan') - labelSpan);
	end
end

function p.main(frame)
	local args = getArgs(frame, {
		removeBlanks = false,
		wrappers = 'Template:Navbox timeline'
	})
	local targs = {
		listpadding = '0'
	}
	-- Arguments to passthrough to navbox
	local passthrough = {
		'name', 'title', 'above', 'below', 'state', 'navbar', 'border', 'image',
		'imageleft', 'style', 'bodystyle', 'style', 'bodystyle', 'basestyle',
		'titlestyle', 'abovestyle', 'belowstyle', 'imagestyle',
		'imageleftstyle', 'titleclass', 'aboveclass', 'bodyclass', 'belowclass',
		'imageclass'
	}
	local rows = timelineInfo(args)
	local wrapper = mw.html.create('table')
		:addClass('timeline-wrapper')
	local tbl = wrapper:tag('tr')
		:tag('td')
			:addClass('timeline-wrapper-cell')
			:tag('table')
				:addClass('timeline-table')
	
	renderDates(args, tbl, rows)
	renderTimeline(args, tbl, rows)
	
	if yesno(args.footer) then
		renderDates(args, tbl, rows, true)
	end
	
	for _, name in ipairs(passthrough) do 
		targs[name] = args[name]
	end

	targs.templatestyles = 'Module:Navbox timeline/styles.css'
	targs.list1 = tostring(wrapper)
	
	return navbox(targs)
end

return p