<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://wiki.shroudforge.net/index.php?action=history&amp;feed=atom&amp;title=Module%3ACountdown</id>
	<title>Module:Countdown - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.shroudforge.net/index.php?action=history&amp;feed=atom&amp;title=Module%3ACountdown"/>
	<link rel="alternate" type="text/html" href="https://wiki.shroudforge.net/index.php?title=Module:Countdown&amp;action=history"/>
	<updated>2026-11-05T02:41:17Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.46.2</generator>
	<entry>
		<id>https://wiki.shroudforge.net/index.php?title=Module:Countdown&amp;diff=46&amp;oldid=prev</id>
		<title>Bonsaibauer: 1 revision imported: Import of Star-Citizen-Modules</title>
		<link rel="alternate" type="text/html" href="https://wiki.shroudforge.net/index.php?title=Module:Countdown&amp;diff=46&amp;oldid=prev"/>
		<updated>2026-11-04T21:58:42Z</updated>

		<summary type="html">&lt;p&gt;1 revision imported: Import of Star-Citizen-Modules&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw-interface=&quot;&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en-GB&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 21:58, 4 November 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;en-GB&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Bonsaibauer</name></author>
	</entry>
	<entry>
		<id>https://wiki.shroudforge.net/index.php?title=Module:Countdown&amp;diff=45&amp;oldid=prev</id>
		<title>starcitizen&gt;Alistar Bot: Reject a date the formatter cannot read, rather than rendering an empty element nobody notices (via update-page on MediaWiki MCP Server)</title>
		<link rel="alternate" type="text/html" href="https://wiki.shroudforge.net/index.php?title=Module:Countdown&amp;diff=45&amp;oldid=prev"/>
		<updated>2026-08-22T00:02:48Z</updated>

		<summary type="html">&lt;p&gt;Reject a date the formatter cannot read, rather than rendering an empty element nobody notices (via update-page on MediaWiki MCP Server)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;require(&amp;#039;strict&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
--- @module Countdown&lt;br /&gt;
--- A live countdown: the hook a gadget animates, plus a fallback that is true&lt;br /&gt;
--- without JavaScript.&lt;br /&gt;
---&lt;br /&gt;
--- It deliberately does NOT compute the remaining time here. A value worked out&lt;br /&gt;
--- at parse time is frozen into the parser cache and can be arbitrarily stale —&lt;br /&gt;
--- and a countdown reading &amp;quot;2 days left&amp;quot; for an event that finished last week is&lt;br /&gt;
--- worse than no countdown at all. So the dates go out as data attributes, the&lt;br /&gt;
--- mainpage gadget does the arithmetic on load, and a reader without JavaScript&lt;br /&gt;
--- sees the date range, which stays true forever.&lt;br /&gt;
---&lt;br /&gt;
--- The module owns the markup and the styling; the gadget owns the numbers. That&lt;br /&gt;
--- is the same division Module:Entity&amp;#039;s stat line uses, where the server renders&lt;br /&gt;
--- a plausible figure and the gadget corrects it — except here the honest&lt;br /&gt;
--- fallback is a different fact rather than a stale version of the same one.&lt;br /&gt;
&lt;br /&gt;
local START_ATTR = &amp;#039;data-gadget-mainpage-countdown-start&amp;#039;&lt;br /&gt;
local END_ATTR = &amp;#039;data-gadget-mainpage-countdown-end&amp;#039;&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
--- @param iso string&lt;br /&gt;
--- @return string|nil formatted, or nil when the date cannot be read&lt;br /&gt;
local function formatDate(iso, format)&lt;br /&gt;
	if not iso or iso == &amp;#039;&amp;#039; then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
	local ok, formatted = pcall(function()&lt;br /&gt;
		return mw.language.getContentLanguage():formatDate(format, iso)&lt;br /&gt;
	end)&lt;br /&gt;
	return ok and formatted or nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Rejects a date the formatter cannot read. A typo has to be loud: silently&lt;br /&gt;
--- rendering nothing is the one outcome nobody notices, and this element&amp;#039;s whole&lt;br /&gt;
--- job is to be the page&amp;#039;s most conspicuous statement about time.&lt;br /&gt;
---&lt;br /&gt;
--- @param name string parameter name, for the message&lt;br /&gt;
--- @param iso string&lt;br /&gt;
local function assertReadable(name, iso)&lt;br /&gt;
	if not formatDate(iso, &amp;#039;j F Y&amp;#039;) then&lt;br /&gt;
		error(&amp;#039;Countdown: &amp;#039; .. name .. &amp;#039; is not a readable date: &amp;#039; .. tostring(iso))&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- The no-JS reading: a date, or a range when both ends are known. The year is&lt;br /&gt;
--- dropped from the opening date when both fall in the same one, so the common&lt;br /&gt;
--- case reads &amp;quot;12 – 18 August 2026&amp;quot; rather than repeating itself.&lt;br /&gt;
---&lt;br /&gt;
--- @param starts string|nil&lt;br /&gt;
--- @param ends string&lt;br /&gt;
--- @return string&lt;br /&gt;
local function fallbackText(starts, ends)&lt;br /&gt;
	local endText = formatDate(ends, &amp;#039;j F Y&amp;#039;)&lt;br /&gt;
	if not endText then&lt;br /&gt;
		return &amp;#039;&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	if not starts or starts == &amp;#039;&amp;#039; then&lt;br /&gt;
		return endText&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local sameYear = formatDate(starts, &amp;#039;Y&amp;#039;) == formatDate(ends, &amp;#039;Y&amp;#039;)&lt;br /&gt;
	local startText = formatDate(starts, sameYear and &amp;#039;j F&amp;#039; or &amp;#039;j F Y&amp;#039;)&lt;br /&gt;
	if not startText then&lt;br /&gt;
		return endText&lt;br /&gt;
	end&lt;br /&gt;
	return startText .. &amp;#039; – &amp;#039; .. endText&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- @class CountdownProps&lt;br /&gt;
--- @field starts? string  ISO 8601 start. Omit for something already under way;&lt;br /&gt;
---        with it the gadget can also render the &amp;quot;starts in&amp;quot; tense.&lt;br /&gt;
--- @field ends string     ISO 8601 end. Required.&lt;br /&gt;
--- @field label? string   Overrides the gadget&amp;#039;s tense-derived label. Rarely&lt;br /&gt;
---        wanted — the whole point is that the label follows the dates.&lt;br /&gt;
--- @field class? string&lt;br /&gt;
&lt;br /&gt;
--- @param props CountdownProps&lt;br /&gt;
--- @return string&lt;br /&gt;
function p.render(props)&lt;br /&gt;
	if not props.ends or props.ends == &amp;#039;&amp;#039; then&lt;br /&gt;
		error(&amp;#039;Countdown: ends is required&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
	assertReadable(&amp;#039;ends&amp;#039;, props.ends)&lt;br /&gt;
	if props.starts and props.starts ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		assertReadable(&amp;#039;starts&amp;#039;, props.starts)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local styles = mw.getCurrentFrame():extensionTag({&lt;br /&gt;
		name = &amp;#039;templatestyles&amp;#039;,&lt;br /&gt;
		args = { src = &amp;#039;Module:Countdown/styles.css&amp;#039; },&lt;br /&gt;
	})&lt;br /&gt;
&lt;br /&gt;
	local root = mw.html.create(&amp;#039;div&amp;#039;):addClass(&amp;#039;t-countdown&amp;#039;)&lt;br /&gt;
	if props.class and props.class ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		root:addClass(props.class)&lt;br /&gt;
	end&lt;br /&gt;
	root:attr(END_ATTR, props.ends)&lt;br /&gt;
	if props.starts and props.starts ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		root:attr(START_ATTR, props.starts)&lt;br /&gt;
	end&lt;br /&gt;
	if props.label and props.label ~= &amp;#039;&amp;#039; then&lt;br /&gt;
		root:attr(&amp;#039;data-gadget-mainpage-countdown-label&amp;#039;, props.label)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- The fallback is a real sentence-worth of fact, not an empty shell: if the&lt;br /&gt;
	-- gadget never runs, this is what the reader is left with.&lt;br /&gt;
	root:tag(&amp;#039;div&amp;#039;):addClass(&amp;#039;t-countdown__fallback&amp;#039;):wikitext(fallbackText(props.starts, props.ends))&lt;br /&gt;
&lt;br /&gt;
	return styles .. tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Wikitext entry point.&lt;br /&gt;
---&lt;br /&gt;
--- @param frame mw.frame&lt;br /&gt;
--- @return string&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	local args = require(&amp;#039;Module:Arguments&amp;#039;).getArgs(frame)&lt;br /&gt;
	return p.render({&lt;br /&gt;
		starts = args.starts,&lt;br /&gt;
		ends = args.ends,&lt;br /&gt;
		label = args.label,&lt;br /&gt;
		class = args.class,&lt;br /&gt;
	})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>starcitizen&gt;Alistar Bot</name></author>
	</entry>
</feed>