Module:Timeline
Documentation for this module may be created at Module:Timeline/doc
local p = {}
local dayLabels = {
"Early January", "January--February", "February--March", "March--April",
"April--May", "May--June", "June", "June--July", "July--August",
"August--September", "September--October", "October--November",
"November--December", "Late December"
}
function p.render(frame)
local year = tonumber(frame.args["Year"])
local category = frame.args["Category"]
local categoryUpper = string.upper(category)
-- Query Cargo using preprocess to avoid argument table errors
local query = string.format(
'<div>{{#cargo_query:tables=NewsPosts|fields=Page_Name,Title,Author,Publisher,DayNum|where=YearNum=%d AND Type="%s News"|order by=RealDate ASC}}</div>',
year, categoryUpper
)
local cargoHTML = frame:preprocess(query)
local events = {}
for i = 1, 14 do events[i] = {} end
-- Extract rows from the raw HTML Cargo outputs
for row in string.gmatch(cargoHTML, "<tr>(.-)</tr>") do
-- Matches the 5 columns we requested
local page, title, author, publisher, day = string.match(row, "<td>(.-)</td>%s*<td>(.-)</td>%s*<td>(.-)</td>%s*<td>(.-)</td>%s*<td>(.-)</td>")
if day then
local d = tonumber(day)
if d and d >= 1 and d <= 14 then
-- If Author is empty, use Publisher
local charName = (author ~= "" and author) or publisher
table.insert(events[d], {
Post = title,
Page = page,
Char = charName,
Date = "Auto" -- Handled by Lua calculation below
})
end
end
end
-- Add manual events from Day1...Day14 parameters (for events without wiki pages)
for i = 1, 14 do
local manualData = frame.args["Day" .. i]
if manualData and manualData ~= "" then
for line in string.gmatch(manualData, "[^\r\n]+") do
local post, char = string.match(line, "^%s*(.-)%s*;%s*(.-)%s*$")
if post and post ~= "" then
table.insert(events[i], { Post = post, Char = char or "" })
elseif post == nil and line ~= "" then
table.insert(events[i], { Post = line, Char = "" })
end
end
end
end
-- Build the beautiful HTML table
local html = {}
-- Determine accent color based on category
local headerBg = "#3a506b" -- Default slate
if categoryUpper == "CHARACTER" then headerBg = "#6e473b" end
table.insert(html, '<div class="aquila-timeline-custom" style="border: 1px solid #d1d5db; border-radius: 12px; overflow: hidden; margin-bottom: 2em; box-shadow: 0 4px 12px rgba(0,0,0,0.05);">')
table.insert(html, '<table style="width: 100%; border-collapse: collapse; background: #ffffff; margin: 0;">')
-- Table Header
table.insert(html, '<tr>')
table.insert(html, '<th colspan="4" style="background: ' .. headerBg .. '; color: #ffffff; font-family: \'Georgia\', serif; font-size: 1.1em; letter-spacing: 0.1em; text-align: center; padding: 12px; border-bottom: 3px solid #e0a96d;">' .. categoryUpper .. ' NEWS</th>')
table.insert(html, '</tr>')
-- Column Headers
table.insert(html, '<tr>')
table.insert(html, '<th style="background: #f8fafc; color: #1e293b; font-family: \'Georgia\', serif; padding: 10px; text-align: left; border-bottom: 2px solid #e2e8f0; width: 20%;">In-universe date</th>')
table.insert(html, '<th style="background: #f8fafc; color: #1e293b; font-family: \'Georgia\', serif; padding: 10px; text-align: left; border-bottom: 2px solid #e2e8f0; width: 35%;">Post</th>')
table.insert(html, '<th style="background: #f8fafc; color: #1e293b; font-family: \'Georgia\', serif; padding: 10px; text-align: left; border-bottom: 2px solid #e2e8f0; width: 25%;">Character / nation</th>')
table.insert(html, '<th style="background: #f8fafc; color: #1e293b; font-family: \'Georgia\', serif; padding: 10px; text-align: left; border-bottom: 2px solid #e2e8f0; width: 20%;">Real-world date</th>')
table.insert(html, '</tr>')
-- Loop through days
local skipRows = 0
for i = 1, 14 do
if skipRows > 0 then
skipRows = skipRows - 1
else
local eventCount = #events[i]
if eventCount == 0 then
-- Empty Day Logic (merges consecutive empty days)
local emptySpan = 1
for j = i + 1, 14 do
if #events[j] == 0 then
emptySpan = emptySpan + 1
else
break
end
end
local dateLabel = dayLabels[i]
if emptySpan > 1 then
dateLabel = dayLabels[i] .. " to " .. dayLabels[i + emptySpan - 1]
end
dateLabel = dateLabel .. ", Year " .. year
table.insert(html, '<tr style="background: #fcfcfd;">')
table.insert(html, '<td style="padding: 12px 10px; border-bottom: 1px solid #e2e8f0; color: #64748b; font-style: italic; font-size: 0.9em;">' .. dateLabel .. '</td>')
table.insert(html, '<td colspan="3" style="padding: 12px 10px; border-bottom: 1px solid #e2e8f0; color: #94a3b8; font-style: italic;">No news</td>')
table.insert(html, '</tr>')
skipRows = emptySpan - 1
else
-- Event Logic
local rowspanAttr = ''
if eventCount > 1 then
rowspanAttr = ' rowspan="' .. eventCount .. '" '
end
for idx, event in ipairs(events[i]) do
table.insert(html, '<tr style="background: #ffffff;">')
-- Date Column (Only on first row of the day)
if idx == 1 then
table.insert(html, '<td' .. rowspanAttr .. 'style="padding: 12px 10px; border-bottom: 1px solid #e2e8f0; border-right: 1px solid #e2e8f0; font-weight: bold; color: #1e293b; vertical-align: top;">' .. dayLabels[i] .. ', Year ' .. year .. '</td>')
end
-- Post Column
local postText = event.Post
if event.Page and event.Page ~= "" then
postText = '[[' .. event.Page .. '|' .. event.Post .. ']]'
elseif not string.find(postText, "%[%[") and not string.find(postText, "<span>") then
postText = '[[' .. postText .. ']]'
end
table.insert(html, '<td style="padding: 12px 10px; border-bottom: 1px solid #e2e8f0; border-right: 1px solid #e2e8f0; vertical-align: top;">' .. postText .. '</td>')
-- Character Column
local charText = event.Char or ""
if charText ~= "" and not string.find(charText, "%[%[") and not string.find(charText, "<span>") then
charText = '[[' .. charText .. ']]'
end
table.insert(html, '<td style="padding: 12px 10px; border-bottom: 1px solid #e2e8f0; border-right: 1px solid #e2e8f0; vertical-align: top;">' .. charText .. '</td>')
-- Real Date Column
local realDate = event.Date or ""
if realDate == "Auto" then
-- Year 1, Day 1 is 2026-07-28. Every year is 14 days.
local daysOffset = ((year - 1) * 14) + (i - 1)
local timestamp = os.time({year=2026, month=07, day=28, hour=12}) + (daysOffset * 86400)
realDate = os.date("%Y-%m-%d", timestamp)
end
table.insert(html, '<td style="padding: 12px 10px; border-bottom: 1px solid #e2e8f0; vertical-align: top; color: #64748b; font-size: 0.9em; white-space: nowrap;">' .. realDate .. '</td>')
table.insert(html, '</tr>')
end
end
end
end
table.insert(html, '</table>')
table.insert(html, '</div>')
return table.concat(html, "\n")
end
return p