Module:Timeline

Jump to navigation Jump to search

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


require('Module:No globals')

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

-- Add a blank table cell
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)
		-- Divide the cell up every decade if showing decades at the top
		else
			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 entries, start years, and end years
local function timelineInfo(args)
	local info = {
		startYear = math.huge,
		startYears = {},
		endYear = 0,
		endYears = {},
		entries = {}
	}
	
	for k, _ in pairs(args) do
		if type(k) == 'string' then
			local num = k:match('^%a+(%d+)$')

			if num then
				table.insert(info.entries, tonumber(num))
			end
		end
	end
	
	table.sort(info.entries)
	
	for i, num in ipairs(info.entries) do 
		if args['item' .. i] then
			if not args['date' .. i] then
				error('item' .. i .. ' requires a corresponding ' .. 'date' .. i, 0)
			end
			
			local dates = mw.text.split(args['date' .. i], '-', true)
			local startYear = tonumber(dates[1])
			local endYear = tonumber(dates[2]) or tonumber(os.date('%Y')) + 1
			
			if not startYear then
				error('date' .. i .. ' contains an invalid timerange', 0)
			end
			
			info.startYear = math.min(info.startYear, startYear)
			info.endYear = math.max(info.endYear, endYear)
			info.startYears[i] = startYear
			info.endYears[i] = endYear
		end
	end
	
	if args.startoffset then
		info.startYear = info.startYear - tonumber(args.startoffset)
	end
	
	if args.startyear then
		info.startYear = math.min(info.startYear, tonumber(args.startyear))
	end
	
	if args.endoffset then
		info.endYear = info.endYear + tonumber(args.endoffset)
	end
	
	if args.endyear then
		info.endYear = math.max(info.endYear, tonumber(args.endyear))
	end
	
	return info
end

-- Render the date rows
local function renderDates(args, tbl, info)
	local showDecades = yesno(args.decades)
	local labelRow = nil
	
	if args.label then
		labelRow = 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 '')
	end
	
	-- Render the decades row
	if showDecades ~= false then
		local decadeRow = tbl:tag('tr')
		local year = info.startYear
		
		decadeRow:node(labelRow)
		
		while year < info.endYear do
			local dur = math.min(10 - year % 10, info.endYear - 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

	-- Render the years row
	local yearRow = tbl:tag('tr')
	local width = 100 / (info.endYear - info.startYear)
	
	if showDecades == false then
		yearRow:node(labelRow)
	end
	
	for i = info.startYear, info.endYear - 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, info)
	local row = nil
	local prev = info.startYear
	local prevItem = nil
	local prevLabel = nil
	local labelSpan = 0
	
	for i, num in ipairs(info.entries) do
		if args['row' .. i] or row == nil then
			addBlank(args, row, prev, info.endYear)
			
			row = tbl:tag('tr')
			prev = info.startYear
			
			if labelSpan <= 0 and args.label then
				labelSpan = tonumber(args['span' .. i]) or 1
				
				prevLabel = row:tag('th')
					:attr('scope', 'row')
					:attr('rowspan', labelSpan)
					:addClass('navbox-group timeline-label')
					:cssText(args.labelstyle)
					:cssText(args['labelstyle' .. i] or '')
					:wikitext(args['row' .. i])
			end
			
			labelSpan = labelSpan - 1
		end
		
		if args['item' .. i] then
			local content = args['item' .. i] 
			local startYear = info.startYears[i]
			local endYear = info.endYears[i]
			
			addBlank(args, row, prev, startYear)
			
			-- Shrink previous item so new item can start at the start year
			if prevItem and prev > startYear then
				prevItem:attr('colspan', prevItem:getAttr('colspan') - prev + startYear);
			end
			
			prevItem = row:tag('td')
				:addClass('timeline-item')
				:cssText(args.itemstyle)
				:cssText(args['style' .. i] or '')
				:attr('colspan', endYear - startYear)
				:wikitext(content)
			
			prev = endYear
		end
	end
	
	-- Remove any extra rowspan from the label
	if prevLabel and labelSpan > 0 then
		prevLabel:attr('rowspan', prevLabel:getAttr('rowspan') - labelSpan);
	end
	
	addBlank(args, row, prev, info.endYear)
end

function p.main(frame)
	local args = getArgs(frame, {
		removeBlanks = false,
		wrappers = 'Template:Timeline'
	})
	local targs = {
		listpadding = '0'
	}
	-- Arguments to passthrough to navbox
	local passthrough = {
		'name', 'title', 'above', 'below', 'state', 'navbar', 'border', 1,
		'image', 'imageleft', 'style', 'bodystyle', 'style', 'bodystyle',
		'basestyle', 'titlestyle', 'abovestyle', 'belowstyle', 'imagestyle',
		'imageleftstyle', 'titleclass', 'aboveclass', 'bodyclass',
		'belowclass', 'imageclass'
	}
	local info = timelineInfo(args)
	local tbl = mw.html.create('table'):addClass('timeline-table')
	
	renderDates(args, tbl, info)
	renderTimeline(args, tbl, info)
	
	if yesno(args.footer) then
		renderDates(args, tbl, info)
	end
	
	for _, name in ipairs(passthrough) do 
		targs[name] = args[name]
	end
	
	targs.list1 = frame:extensionTag{
		name = 'templatestyles',
		args = {
			src = 'Template:Timeline/style.css'
		}
	} .. tostring(tbl)
	
	return navbox(targs)
end

return p