Module:Timeline
From Halopedia, the Halo wiki
Documentation for this module may be created at ModuleDoc:Timeline
local p = {}
-- Cache for page existence checks
local existenceCache = {}
-- This checks if a wiki page for a given year exists.
local function isValidYearPage(key)
local title = mw.title.new(key)
if not title or not title.exists then
return false
end
return true
end
local function pageExists(year)
local key = tostring(year)
if existenceCache[key] ~= nil then
return existenceCache[key]
end
local exists = isValidYearPage(key)
existenceCache[key] = exists
return exists
end
function p.main(frame)
local args = frame:getParent().args
local title = mw.title.getCurrentTitle().text
-- Detect year and era (Example: 2552, 480 BCE, so on)
local yearStr = args.year or title
local yearNum, era
-- This checks if the year is BCE or CE
if yearStr:match("%d+%s*[BCE]+$") then
yearNum = tonumber(yearStr:match("%d+"))
era = "BCE"
else
yearNum = tonumber(yearStr:match("%d+")) or 0
era = "CE"
end
-- This stops 0 from being used.
if yearNum == 0 then
return '<!-- Invalid year -->'
end
-- Parse ignore list
local ignoreSet = {}
if args.ignore and args.ignore ~= '' then
for y in mw.text.gsplit(args.ignore, ",", true) do
y = mw.text.trim(y)
local num = tonumber(y:match("%d+"))
if num then ignoreSet[num] = true end
end
end
-- Use this to style the template.
local html = mw.html.create('div')
:addClass('infobox')
:css({
float = 'right',
width = '300px',
margin = '0 0 1em 1em',
padding = '8px',
border = '1px solid #aaa',
background = '#f9f9f9',
['border-radius'] = '4px'
})
-- Timeline Logo Header
local logoClass = ''
if args.image and args.image ~= '' then
logoClass = 'notpageimage'
end
html:tag('div')
:css('text-align', 'center')
:css('margin-bottom', '10px')
:wikitext('[[File:HP-Timeline.png|300px|link=Timeline|alt=Timeline|class=' .. logoClass .. ']]')
-- Year Header
local displayYear = (era == "BCE") and (yearNum .. " BCE") or tostring(yearNum)
html:tag('div')
:addClass('infobox-header')
:css('text-align', 'center')
:css('font-weight', 'bold')
:css('font-size', '200%')
:wikitext(displayYear)
-- Year image + caption
if args.image and args.image ~= '' then
html:tag('div')
:css('text-align', 'center')
:css('margin', '10px 0 6px 0')
:wikitext('[[' .. args.image .. '|300px]]')
if args.caption and args.caption ~= '' then
html:tag('div')
:css('text-align', 'center')
:css('font-size', 'larger')
:css('margin-top', '4px')
:wikitext(args.caption)
end
end
-- Other calendars
if args.other and args.other ~= '' then
html:tag('div')
:css('margin-top', '10px')
:css('text-align', 'center')
:wikitext(args.other)
end
-- Override code - CIA note: This is needed for years so stuff doesnt break with timespans that go beyond the capabilities of Mediawiki.
local manual = {
previous1 = args.previous1 or args.manualprevious1,
previous2 = args.previous2 or args.manualprevious2,
next1 = args.next1 or args.manualnext1,
next2 = args.next2 or args.manualnext2
}
-- Helper to check if we're near the BCE/CE boundary (only affects ~199 BCE and ~199 CE)
local function isNearEraTransition(year)
return year <= 200
end
local function findNearestPrev(year, currentEra)
local maxSearch = 200
if currentEra == "BCE" then
local y = year + 1
for _ = 1, maxSearch do
if y > 3000 then break end
if y ~= year and not ignoreSet[y] and pageExists(y .. " BCE") then
return y, "BCE"
end
y = y + 1
end
else
local y = year - 1
for _ = 1, maxSearch do
if y < 1 then break end
if y ~= year and not ignoreSet[y] and pageExists(tostring(y)) then
return y, "CE"
end
y = y - 1
end
end
-- Only cross to BCE when near the boundary AND we are in CE
if currentEra == "CE" and isNearEraTransition(year) then
local y = 1
for _ = 1, maxSearch do
if y > 3000 then break end
if y ~= year and not ignoreSet[y] and pageExists(y .. " BCE") then
return y, "BCE"
end
y = y + 1
end
end
return nil
end
local function findNearestNext(year, currentEra)
local maxSearch = 200
if currentEra == "BCE" then
local y = year - 1
for _ = 1, maxSearch do
if y < 1 then break end
if y ~= year and not ignoreSet[y] and pageExists(y .. " BCE") then
return y, "BCE"
end
y = y - 1
end
else
local y = year + 1
for _ = 1, maxSearch do
if y > 3000 then break end
if y ~= year and not ignoreSet[y] and pageExists(tostring(y)) then
return y, "CE"
end
y = y + 1
end
end
-- Only cross to CE when near the boundary AND we are in BCE
if currentEra == "BCE" and isNearEraTransition(year) then
local y = 1
for _ = 1, maxSearch do
if y > 3000 then break end
if y ~= year and not ignoreSet[y] and pageExists(tostring(y)) then
return y, "CE"
end
y = y + 1
end
end
return nil
end
local function makeLink(y, text, isBold, targetEra)
if not y then return nil end
local page = (targetEra == "BCE") and (y .. " BCE") or tostring(y)
local display = (targetEra == "BCE") and (y .. " BCE") or tostring(y)
if text then display = text end
local link = '[[' .. page .. '|' .. display .. ']]'
return isBold and "'''" .. link .. "'''" or link
end
-- === SMART NAVIGATION (THIS SECTION SUCKS OH MY GOONDESS) ===
local nav = html:tag('div')
:css('margin-top', '12px')
:css('text-align', 'center')
:css('font-size', '110%')
if era == "BCE" then
local farPrev = manual.previous1 or findNearestPrev(findNearestPrev(yearNum, "BCE") or yearNum, "BCE")
local nearPrev = manual.previous2 or findNearestPrev(yearNum, "BCE")
local nearNextYear, nearNextEra = findNearestNext(yearNum, "BCE")
local farNextYear, farNextEra = nil, nil
if nearNextYear then
farNextYear, farNextEra = findNearestNext(nearNextYear, nearNextEra)
end
local navTable = nav:tag('table')
:css('width', '100%')
:css('border-collapse', 'collapse')
local row = navTable:tag('tr')
-- Left side (previous years)
local leftCell = row:tag('td')
:css('text-align', 'right')
:css('width', '45%')
:css('padding-right', '8px')
local leftParts = {}
if farPrev then table.insert(leftParts, makeLink(farPrev, nil, false, "BCE")) end
if nearPrev then table.insert(leftParts, makeLink(nearPrev, nil, false, "BCE")) end
leftCell:wikitext(table.concat(leftParts, " • "))
-- Center (current year - bold, no link)
local centerCell = row:tag('td')
:css('text-align', 'center')
:css('font-weight', 'bold')
:css('width', '10%')
:css('white-space', 'nowrap')
-- Right side (next years)
local rightCell = row:tag('td')
:css('text-align', 'left')
:css('width', '45%')
:css('padding-left', '8px')
local rightParts = {}
if nearNextYear then table.insert(rightParts, makeLink(nearNextYear, nil, false, nearNextEra)) end
if farNextYear then table.insert(rightParts, makeLink(farNextYear, nil, false, farNextEra)) end
rightCell:wikitext(table.concat(rightParts, " • "))
local centerText = displayYear
if #leftParts > 0 then
centerText = " • " .. centerText
end
if #rightParts > 0 then
centerText = centerText .. " • "
end
centerCell:wikitext(centerText)
else
local nearPrevYear, nearPrevEra = findNearestPrev(yearNum, "CE")
local farPrevYear, farPrevEra = nil, nil
if nearPrevYear then
farPrevYear, farPrevEra = findNearestPrev(nearPrevYear, nearPrevEra)
end
local nearNext = manual.next1 or findNearestNext(yearNum, "CE")
local farNext = manual.next2 or findNearestNext(nearNext or yearNum, "CE")
local navTable = nav:tag('table')
:css('width', '100%')
:css('border-collapse', 'collapse')
local row = navTable:tag('tr')
-- Left side
local leftCell = row:tag('td')
:css('text-align', 'right')
:css('width', '45%')
:css('padding-right', '8px')
local leftParts = {}
if farPrevYear then table.insert(leftParts, makeLink(farPrevYear, nil, false, farPrevEra)) end
if nearPrevYear then table.insert(leftParts, makeLink(nearPrevYear, nil, false, nearPrevEra)) end
leftCell:wikitext(table.concat(leftParts, " • "))
-- Center (current year)
local centerCell = row:tag('td')
:css('text-align', 'center')
:css('font-weight', 'bold')
:css('width', '10%')
:css('white-space', 'nowrap')
-- Right side
local rightCell = row:tag('td')
:css('text-align', 'left')
:css('width', '45%')
:css('padding-left', '8px')
local rightParts = {}
if nearNext then table.insert(rightParts, makeLink(nearNext, nil, false, "CE")) end
if farNext then table.insert(rightParts, makeLink(farNext, nil, false, "CE")) end
rightCell:wikitext(table.concat(rightParts, " • "))
local centerText = displayYear
if #leftParts > 0 then
centerText = " • " .. centerText
end
if #rightParts > 0 then
centerText = centerText .. " • "
end
centerCell:wikitext(centerText)
end
-- Decade Grid with 0s exception
local decadeStart = math.floor(yearNum / 10) * 10
local decadeLabel = tostring(decadeStart) .. "s"
if era == "BCE" then decadeLabel = decadeLabel .. " BCE" end
local decadeDiv = html:tag('div')
:css('margin-top', '14px')
:css('text-align', 'center')
:css('font-size', '110%')
:css('line-height', '1.8')
decadeDiv:wikitext("'''Years in the " .. decadeLabel .. "'''<br>")
local firstRowStart = (decadeStart == 0 and era == "CE") and 1 or 0
for i = firstRowStart, 4 do
local y = decadeStart + i
local text = tostring(y) .. (era == "BCE" and " BCE" or "")
local link
if y == yearNum then
link = "'''" .. text .. "'''"
elseif pageExists((era == "BCE") and (y .. " BCE") or tostring(y)) then
link = makeLink(y, text, false, era)
else
link = '<span style="color:#888">' .. text .. '</span>'
end
decadeDiv:wikitext(link)
if i < 4 then decadeDiv:wikitext(' • ') end
end
decadeDiv:wikitext('<br>')
for i = 5, 9 do
local y = decadeStart + i
local text = tostring(y) .. (era == "BCE" and " BCE" or "")
local link
if y == yearNum then
link = "'''" .. text .. "'''"
elseif pageExists((era == "BCE") and (y .. " BCE") or tostring(y)) then
link = makeLink(y, text, false, era)
else
link = '<span style="color:#888">' .. text .. '</span>'
end
decadeDiv:wikitext(link)
if i < 9 then decadeDiv:wikitext(' • ') end
end
-- Footer
html:tag('div')
:css('margin-top', '12px')
:css('text-align', 'center')
:css('font-size', '85%')
:wikitext("For a complete list, see the [[:Category:Timeline|timeline category]].")
return html
end
return p