Jump to content

Module:Navbox timeline/sandbox

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by BrandonXLF (talk | contribs) at 04:52, 9 June 2022. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
require('Module:No globals')

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)
		-- 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 row data, cell, start year, and end year
local function parseRow(rowNumber, text)
	if text:sub(-1) ~= '\n' then
		text = text .. '\n'
	end
	
	local minYear = math.huge
	local maxYear = 0
	local cells = {}
	local rowData = {}
	local lineNumber = 0
	
	for line in text:gmatch('(.-)\n') do
		local key, val = line:match('^([a-zA-Z]+): *(.*)$')
		
		lineNumber = lineNumber + 1
		
		-- Row data key value pairs
		if #cells == 0 and key then
			rowData[key] = val

		-- Data cell key value pairs
		elseif key then
			cells[#cells][key] = val
			
		-- Date (cell with item already)
		elseif #cells > 0 and line ~= '' and cells[#cells].item and not cells[#cells].startYear then
			local dates = mw.text.split(line, '-', true)
			local startYear = tonumber(dates[1])
			local endYear = tonumber(dates[2]) or tonumber(os.date('%Y')) + 1
			
			if not startYear then
				error('Invalid timerange at argument ' .. rowNumber .. ' line ' .. lineNumber , 0)
			end

			cells[#cells].startYear = startYear
			cells[#cells].endYear = endYear
			
			if startYear < minYear then
				minYear = startYear
			end

			if endYear > maxYear then
				maxYear = endYear
			end
			
		-- Item
		elseif line ~= '' then
			table.insert(cells, {
				item = line
			})
		end
	end
	
	return rowData, cells, minYear, maxYear
end

-- Get timeline rows, start year, and end year
local function timelineInfo(args)
	local minYear = math.huge
	local maxYear = 0
	local rows = {}
	
	for k, v in ipairs(args) do
		local rowData, cells, rowMinYear, rowMaxYear =  parseRow(k, v)

		if rowMinYear < minYear then
			minYear = rowMinYear
		end

		if rowMaxYear > maxYear then
			maxYear = rowMaxYear
		end

		table.insert(rows, {
			data = rowData,
			cells = cells
		})
	end
	
	if args.startoffset then
		minYear = minYear - tonumber(args.startoffset)
	end
	
	if args.startyear and tonumber(args.startyear) < minYear then
		minYear = tonumber(args.startyear)
	end
	
	if args.endoffset then
		maxYear = maxYear + tonumber(args.endoffset)
	end
	
	if args.endyear and tonumber(args.endyear) > maxYear then
		maxYear = tonumber(args.endyear)
	end
	
	return rows, minYear, maxYear
end

-- Render the date rows
local function renderDates(args, tbl, startYear, endYear)
	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')
			:addClass('timeline-row')
		local year = startYear
		
		decadeRow:node(labelRow)
		
		while year < endYear do
			local dur = math.min(10 - year % 10, 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')
		:addClass('timeline-row')
	local width = 100 / (endYear - startYear)
	
	if showDecades == false then
		yearRow:node(labelRow)
	end
	
	for i = startYear, 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, rows, minYear, maxYear)
	local rowElement = nil
	local rowSuffix = nil
	local prev = minYear
	local prevItem = nil
	local prevLabel = nil
	local labelSpan = 0
	
	for _, row in ipairs(rows) do
		local rowElement = tbl:tag('tr')
			:addClass('timeline-row')
		
		if labelSpan <= 0 and args.label then
			labelSpan = tonumber(row.data.span) or 1
			
			prevLabel = rowElement:tag('th')
				:attr('scope', 'row')
				:attr('rowspan', labelSpan)
				:addClass('navbox-group timeline-label')
				:cssText(args.labelstyle)
				:cssText(row.data.labelstyle)
				:wikitext(row.data.label)
		end
		
		labelSpan = labelSpan - 1
		
		local prevEndYear = minYear
		local prevItem = nil
		
		for _, cell in ipairs(row.cells) 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
			
			-- 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, maxYear)
	end
	
	-- Remove any extra rowspan from the label
	if prevLabel and labelSpan > 0 then
		prevLabel:attr('rowspan', prevLabel:getAttr('rowspan') - labelSpan);
	end
	
	-- Add blanks to thr end of the last row
	-- addBlank(args, rowElement, 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 rows, minYear, maxYear = 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, minYear, maxYear)
	renderTimeline(args, tbl, rows, minYear, maxYear)
	
	if yesno(args.footer) then
		renderDates(args, tbl, minYear, maxYear)
	end
	
	for _, name in ipairs(passthrough) do 
		targs[name] = args[name]
	end

	targs.list1 = frame:extensionTag{
		name = 'templatestyles',
		args = {
			src = 'Module:Timeline/styles.css'
		}
	} .. tostring(wrapper)
	
	return navbox(targs)
end

return p