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