Module:Countdown: Difference between revisions
More actions
starcitizen>Alistar Bot Reject a date the formatter cannot read, rather than rendering an empty element nobody notices (via update-page on MediaWiki MCP Server) |
Bonsaibauer (talk | contribs) m 1 revision imported: Import of Star-Citizen-Modules |
||
(No difference)
| |||
Latest revision as of 21:58, 4 November 2026
- formatDateL26
- assertReadableL42
- fallbackTextL55
- p.renderL82
- p.mainL119
|type = website |description = Module:Countdown is a template page used on the Star Citizen Wiki. Templates are pages that are embedded (transcluded) into other pages to allow for the repetition of information. |site_name = Star Citizen Wiki |locale = en-gb }}
A live countdown display, split by responsibility: the module renders the markup, the data attributes, and a no-JS fallback date; a mainpage gadget computes the actual remaining time and animates the digit stack client-side.
Required by Module:Mainpage/Event and Module:Mainpage/Event/Legacy; not invoked from templates.
For module editors
API
p.render(props) returns <templatestyles> + markup, or raises a Lua error when ends is missing or either date is not readable by the content language's date formatter (mw.language:formatDate); both current callers wrap the call in pcall.
| Field | Type | Description |
|---|---|---|
starts
|
ISO 8601 string | Optional. Omit for something already under way; with it the gadget can also render a "starts in" tense. |
ends
|
ISO 8601 string | Required. |
label
|
string
|
Overrides the gadget's tense-derived label. Rarely wanted; the point is that the label follows the dates. |
class
|
string
|
Extra class on the root. |
p.main(frame) is the #invoke entry point (reads starts/ends/label/class via Module:Arguments).
Gotchas
- The module deliberately does not compute remaining time: a value worked out at parse time freezes into the parser cache and can go arbitrarily stale. It emits the dates as
data-gadget-mainpage-countdown-start/-end(+-label) attributes for the gadget, and renders a no-JS fallback: the end date alone, or astarts – endsrange with the start year dropped when both dates share one. - An unreadable date is a hard Lua error, not a silent empty render, because a typo here has to be loud.
Styles
class = 't-countdown--flat' is the module's own modifier for lying the clock down into a horizontal strip regardless of viewport (it also does this automatically below 639px). The gadget replaces the fallback with a live digit stack, adding t-countdown--live and, for colour, one of --upcoming or --running; --ended carries no colour rule at all, deliberately falling back to the root's --color-subtle.
require('strict')
--- @module Countdown
--- A live countdown: the hook a gadget animates, plus a fallback that is true
--- without JavaScript.
---
--- It deliberately does NOT compute the remaining time here. A value worked out
--- at parse time is frozen into the parser cache and can be arbitrarily stale —
--- and a countdown reading "2 days left" for an event that finished last week is
--- worse than no countdown at all. So the dates go out as data attributes, the
--- mainpage gadget does the arithmetic on load, and a reader without JavaScript
--- sees the date range, which stays true forever.
---
--- The module owns the markup and the styling; the gadget owns the numbers. That
--- is the same division Module:Entity's stat line uses, where the server renders
--- a plausible figure and the gadget corrects it — except here the honest
--- fallback is a different fact rather than a stale version of the same one.
local START_ATTR = 'data-gadget-mainpage-countdown-start'
local END_ATTR = 'data-gadget-mainpage-countdown-end'
local p = {}
--- @param iso string
--- @return string|nil formatted, or nil when the date cannot be read
local function formatDate(iso, format)
if not iso or iso == '' then
return nil
end
local ok, formatted = pcall(function()
return mw.language.getContentLanguage():formatDate(format, iso)
end)
return ok and formatted or nil
end
--- Rejects a date the formatter cannot read. A typo has to be loud: silently
--- rendering nothing is the one outcome nobody notices, and this element's whole
--- job is to be the page's most conspicuous statement about time.
---
--- @param name string parameter name, for the message
--- @param iso string
local function assertReadable(name, iso)
if not formatDate(iso, 'j F Y') then
error('Countdown: ' .. name .. ' is not a readable date: ' .. tostring(iso))
end
end
--- The no-JS reading: a date, or a range when both ends are known. The year is
--- dropped from the opening date when both fall in the same one, so the common
--- case reads "12 – 18 August 2026" rather than repeating itself.
---
--- @param starts string|nil
--- @param ends string
--- @return string
local function fallbackText(starts, ends)
local endText = formatDate(ends, 'j F Y')
if not endText then
return ''
end
if not starts or starts == '' then
return endText
end
local sameYear = formatDate(starts, 'Y') == formatDate(ends, 'Y')
local startText = formatDate(starts, sameYear and 'j F' or 'j F Y')
if not startText then
return endText
end
return startText .. ' – ' .. endText
end
--- @class CountdownProps
--- @field starts? string ISO 8601 start. Omit for something already under way;
--- with it the gadget can also render the "starts in" tense.
--- @field ends string ISO 8601 end. Required.
--- @field label? string Overrides the gadget's tense-derived label. Rarely
--- wanted — the whole point is that the label follows the dates.
--- @field class? string
--- @param props CountdownProps
--- @return string
function p.render(props)
if not props.ends or props.ends == '' then
error('Countdown: ends is required')
end
assertReadable('ends', props.ends)
if props.starts and props.starts ~= '' then
assertReadable('starts', props.starts)
end
local styles = mw.getCurrentFrame():extensionTag({
name = 'templatestyles',
args = { src = 'Module:Countdown/styles.css' },
})
local root = mw.html.create('div'):addClass('t-countdown')
if props.class and props.class ~= '' then
root:addClass(props.class)
end
root:attr(END_ATTR, props.ends)
if props.starts and props.starts ~= '' then
root:attr(START_ATTR, props.starts)
end
if props.label and props.label ~= '' then
root:attr('data-gadget-mainpage-countdown-label', props.label)
end
-- The fallback is a real sentence-worth of fact, not an empty shell: if the
-- gadget never runs, this is what the reader is left with.
root:tag('div'):addClass('t-countdown__fallback'):wikitext(fallbackText(props.starts, props.ends))
return styles .. tostring(root)
end
--- Wikitext entry point.
---
--- @param frame mw.frame
--- @return string
function p.main(frame)
local args = require('Module:Arguments').getArgs(frame)
return p.render({
starts = args.starts,
ends = args.ends,
label = args.label,
class = args.class,
})
end
return p