Module:Navbox timeline

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