Home
Random
Log in
Settings
About Rolling Lounge Wiki
Disclaimers
Rolling Lounge Wiki
Search
Editing
Module:Navbox timeline
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
require('strict') local yesno = require('Module:Yesno') local navbox = require('Module:Navbox')._navbox local getArgs = require('Module:Arguments').getArgs local p = {} -- Convert year-month to numeric value for comparison local function dateToNumber(year, month) return year * 12 + (month or 1) - 1 end -- Convert numeric value back to year and month local function numberToDate(num) local year = math.floor(num / 12) local month = (num % 12) + 1 return year, month end -- Get month abbreviation local function getMonthNum(month) return tostring(month) end -- Parse date string (supports YYYY, YYYY-MM, or YYYY-YYYY formats) local function parseDate(dateStr) if not dateStr then return nil, nil end -- Match YYYY-MM format local year, month = dateStr:match('^(%d%d%d%d)%-(%d%d?)$') if year and month then return tonumber(year), tonumber(month) end -- Match YYYY format year = dateStr:match('^(%d%d%d%d)$') if year then return tonumber(year), 1 end return nil, nil end -- Add blank table cells local function addBlank(args, row, prev, current) if row and prev < current then local duration = current - prev if duration > 0 then row:tag('td') :addClass('timeline-blank') :cssText(args.blankstyle) :attr('colspan', duration) end end end -- Get timeline data local function timelineInfo(args) local rows = { [1] = {}, minDate = math.huge, maxDate = -math.huge, hasLabels = false, useMonths = yesno(args.months) ~= 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 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].startDate then local dates = mw.text.split(fullVal, '-', true) local startYear, startMonth, endYear, endMonth if #dates == 2 then -- Handle YYYY-YYYY format startYear, startMonth = parseDate(dates[1]) endYear, endMonth = parseDate(dates[2]) elseif #dates == 3 then -- Handle YYYY-MM-YYYY format startYear, startMonth = tonumber(dates[1]), tonumber(dates[2]) endYear, endMonth = parseDate(dates[3]) elseif #dates == 4 then -- Handle YYYY-MM-YYYY-MM format startYear, startMonth = tonumber(dates[1]), tonumber(dates[2]) endYear, endMonth = tonumber(dates[3]), tonumber(dates[4]) else -- Single date startYear, startMonth = parseDate(fullVal) endYear, endMonth = startYear, (startMonth == 12) and 1 or (startMonth + 1) if startMonth == 12 then endYear = endYear + 1 end end if not startYear then error('Argument ' .. num .. ' is an invalid time range', 0) end -- Set defaults startMonth = startMonth or 1 endMonth = endMonth or 1 endYear = endYear or (tonumber(os.date('%Y')) + 1) local startDate = dateToNumber(startYear, startMonth) local endDate = dateToNumber(endYear, endMonth) if endDate <= startDate then endDate = startDate + 1 -- Minimum duration of 1 month end rows[#rows][cellIndex].startDate = startDate rows[#rows][cellIndex].endDate = endDate rows[#rows][cellIndex].startYear = startYear rows[#rows][cellIndex].startMonth = startMonth rows[#rows][cellIndex].endYear = endYear rows[#rows][cellIndex].endMonth = endMonth if startDate < rows.minDate then rows.minDate = startDate end if endDate > rows.maxDate then rows.maxDate = endDate end -- New item else table.insert(rows[#rows], { item = fullVal }) end end -- Add overrides from arguments if args.startoffset then rows.minDate = rows.minDate - tonumber(args.startoffset) end if args.startyear then local startMonth = tonumber(args.startmonth) or 1 local customStart = dateToNumber(tonumber(args.startyear), startMonth) if customStart < rows.minDate then rows.minDate = customStart end end if args.endoffset then rows.maxDate = rows.maxDate + tonumber(args.endoffset) end if args.endyear then local endMonth = tonumber(args.endmonth) or 12 local customEnd = dateToNumber(tonumber(args.endyear), endMonth) if customEnd > rows.maxDate then rows.maxDate = customEnd end end return rows end -- Render the date rows local function renderDates(args, tbl, rows, invert) local showDecades = yesno(args.decades) local useMonths = rows.useMonths 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 useMonths) and '3' or (showDecades ~= false or useMonths) 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 currentDate = rows.minDate if not invert then decadeRow, yearRow = yearRow, decadeRow end while currentDate < rows.maxDate do local year, month = numberToDate(currentDate) local nextDecade = math.ceil(year / 10) * 10 local decadeEnd = dateToNumber(nextDecade, 1) local dur = math.min(decadeEnd - currentDate, rows.maxDate - currentDate) decadeRow:tag('th') :attr('scope', 'col') :addClass('timeline-decade') :cssText(args.datestyle) :cssText(args.decadestyle) :attr('colspan', dur) :wikitext(math.floor(year / 10) .. '0s') currentDate = currentDate + dur end end -- Create month row if using months local monthRow = nil if useMonths then monthRow = tbl:tag('tr'):addClass('timeline-row') if invert and showDecades ~= false then -- Reorder for footer monthRow, yearRow, decadeRow = decadeRow, monthRow, yearRow end end -- Populate year row local totalDuration = rows.maxDate - rows.minDate local currentDate = rows.minDate while currentDate < rows.maxDate do local year, month = numberToDate(currentDate) local nextYear = dateToNumber(year + 1, 1) local yearDuration = math.min(nextYear - currentDate, rows.maxDate - currentDate) local width = (yearDuration / totalDuration) * 100 yearRow:tag('th') :attr('scope', 'col') :addClass('timeline-year') :cssText(args.datestyle) :cssText(args.yearstyle) :cssText('width:' .. width .. '%') :attr('colspan', yearDuration) :wikitext(tostring(year)) -- Add months for this year if useMonths and monthRow then local yearStart = currentDate while yearStart < nextYear and yearStart < rows.maxDate do local monthYear, monthNum = numberToDate(yearStart) local monthWidth = (1 / totalDuration) * 100 monthRow:tag('th') :attr('scope', 'col') :addClass('timeline-month') :cssText(args.datestyle) :cssText(args.monthstyle) :cssText('width:' .. monthWidth .. '%') :wikitext(getMonthNum(monthNum)) yearStart = yearStart + 1 end end currentDate = nextYear end end -- Render the timeline itself local function renderTimeline(args, tbl, rows) for rowNum, row in ipairs(rows) do local rowElement = tbl:tag('tr'):addClass('timeline-row') if rows.hasLabels or args.label then rowElement:tag('th') :attr('scope', 'row') :addClass('navbox-group timeline-label') :cssText(args.labelstyle) :cssText(row.labelstyle) :wikitext(row.label or '') end local prevEndDate = rows.minDate for cellNum, cell in ipairs(row) do if cell.startDate == nil then error('Missing timerange for row ' .. rowNum .. ' cell ' .. cellNum, 0) end -- Add blanks before the cell addBlank(args, rowElement, prevEndDate, cell.startDate) rowElement:tag('td') :addClass('timeline-item') :cssText(args.itemstyle) :cssText(cell.style or '') :attr('colspan', cell.endDate - cell.startDate) :wikitext(cell.item) prevEndDate = cell.endDate end -- Add blanks to the end of the row addBlank(args, rowElement, prevEndDate, rows.maxDate) end end function p.main(frame) local args = getArgs(frame, { removeBlanks = false, wrappers = 'Template:Navbox timeline' }) local targs = { listpadding = '0' } local passthrough = { 'name', 'title', 'above', 'below', 'state', 'navbar', 'border', 'image', 'imageleft', '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 notheme') 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) targs.bodyclass = (targs.bodyclass or '') .. ' timeline-container' -- Add this line return navbox(targs) end return p
Summary:
Please note that all contributions to Rolling Lounge Wiki are considered to be released under the Creative Commons Attribution-ShareAlike 4.0 International (see
RLWiki:Copyrights
for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Templates used on this page:
Template:En-WP attribution notice
(
edit
)
Module:Navbox timeline/doc
(
edit
)