<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="es">
	<id>https://wikisp.org/wiki/index.php?action=history&amp;feed=atom&amp;title=M%C3%B3dulo%3AMainpage%2FConfig</id>
	<title>Módulo:Mainpage/Config - Historial de revisiones</title>
	<link rel="self" type="application/atom+xml" href="https://wikisp.org/wiki/index.php?action=history&amp;feed=atom&amp;title=M%C3%B3dulo%3AMainpage%2FConfig"/>
	<link rel="alternate" type="text/html" href="https://wikisp.org/wiki/index.php?title=M%C3%B3dulo:Mainpage/Config&amp;action=history"/>
	<updated>2026-09-14T05:33:11Z</updated>
	<subtitle>Historial de revisiones de esta página en la wiki</subtitle>
	<generator>MediaWiki 1.46.0</generator>
	<entry>
		<id>https://wikisp.org/wiki/index.php?title=M%C3%B3dulo:Mainpage/Config&amp;diff=8135&amp;oldid=prev</id>
		<title>Luxbane: Página creada con «require(&#039;strict&#039;)  --- @module Mainpage/Config --- Loads Module:Mainpage/settings.json, the one page an editor changes. --- --- Everything the page shows that is not prose in a module lives there: the --- featured article, the running event, the build chips, the hero, and the two --- link lists. One file rather than a settings template plus a navigation file, --- because the split cost more than it bought — two pages to find, two formats --- to learn, and a deli…»</title>
		<link rel="alternate" type="text/html" href="https://wikisp.org/wiki/index.php?title=M%C3%B3dulo:Mainpage/Config&amp;diff=8135&amp;oldid=prev"/>
		<updated>2026-09-11T23:22:53Z</updated>

		<summary type="html">&lt;p&gt;Página creada con «require(&amp;#039;strict&amp;#039;)  --- @module Mainpage/Config --- Loads &lt;a href=&quot;/wiki/index.php?title=M%C3%B3dulo:Mainpage/settings.json&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;Módulo:Mainpage/settings.json (la página no existe)&quot;&gt;Module:Mainpage/settings.json&lt;/a&gt;, the one page an editor changes. --- --- Everything the page shows that is not prose in a module lives there: the --- featured article, the running event, the build chips, the hero, and the two --- link lists. One file rather than a settings template plus a navigation file, --- because the split cost more than it bought — two pages to find, two formats --- to learn, and a deli…»&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Página nueva&lt;/b&gt;&lt;/p&gt;&lt;div&gt;require(&amp;#039;strict&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
--- @module Mainpage/Config&lt;br /&gt;
--- Loads [[Module:Mainpage/settings.json]], the one page an editor changes.&lt;br /&gt;
---&lt;br /&gt;
--- Everything the page shows that is not prose in a module lives there: the&lt;br /&gt;
--- featured article, the running event, the build chips, the hero, and the two&lt;br /&gt;
--- link lists. One file rather than a settings template plus a navigation file,&lt;br /&gt;
--- because the split cost more than it bought — two pages to find, two formats&lt;br /&gt;
--- to learn, and a delimiter convention for list values that a real array does&lt;br /&gt;
--- not need.&lt;br /&gt;
---&lt;br /&gt;
--- JSON rather than a `#switch` template for three reasons that matter to the&lt;br /&gt;
--- people editing it: MediaWiki refuses to SAVE invalid JSON, so the page&lt;br /&gt;
--- cannot be left broken; a list is a real array, so nothing has to be escaped&lt;br /&gt;
--- or separated; and the build chips are a list of objects rather than&lt;br /&gt;
--- `patch1type` / `patch2type` / `patch3type` flattened into numbered keys.&lt;br /&gt;
---&lt;br /&gt;
--- The cost, paid deliberately: JSON has no comments, so the guidance that used&lt;br /&gt;
--- to sit inline lives in the file&amp;#039;s own `_readme` and in Module:Mainpage/doc.&lt;br /&gt;
&lt;br /&gt;
local DATA_PAGE = &amp;#039;Module:Mainpage/settings.json&amp;#039;&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local data&lt;br /&gt;
&lt;br /&gt;
--- Converts one loaded value into plain Lua.&lt;br /&gt;
---&lt;br /&gt;
--- `mw.loadJsonData` hands back a read-only table whose metatable breaks the&lt;br /&gt;
--- length operator and `next()`. Copying once, here, confines that to this&lt;br /&gt;
--- function: every caller downstream gets an ordinary table it can measure,&lt;br /&gt;
--- iterate and test however it likes.&lt;br /&gt;
---&lt;br /&gt;
--- Two normalisations happen on the way through, so the rest of the module&lt;br /&gt;
--- never has to think about either:&lt;br /&gt;
---  * a blank string becomes nil, so clearing a value in the JSON does the same&lt;br /&gt;
---    thing as deleting its line;&lt;br /&gt;
---  * an array is rebuilt compacted, so a blank entry leaves no hole for&lt;br /&gt;
---    `ipairs` to stop at.&lt;br /&gt;
---&lt;br /&gt;
--- Keys beginning with an underscore are guidance for whoever is editing the&lt;br /&gt;
--- file, not data, and are dropped.&lt;br /&gt;
---&lt;br /&gt;
--- @param value any&lt;br /&gt;
--- @return any&lt;br /&gt;
local function toPlain(value)&lt;br /&gt;
	if type(value) == &amp;#039;string&amp;#039; then&lt;br /&gt;
		local trimmed = mw.text.trim(value)&lt;br /&gt;
		return trimmed ~= &amp;#039;&amp;#039; and trimmed or nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if type(value) ~= &amp;#039;table&amp;#039; then&lt;br /&gt;
		return value&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- An array. `value[1] ~= nil` rather than `#value`, because the first pass&lt;br /&gt;
	-- runs on the loadJsonData table itself, where the length operator lies.&lt;br /&gt;
	if value[1] ~= nil then&lt;br /&gt;
		local list = {}&lt;br /&gt;
		for _, item in ipairs(value) do&lt;br /&gt;
			local converted = toPlain(item)&lt;br /&gt;
			if converted ~= nil then&lt;br /&gt;
				list[#list + 1] = converted&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		return list&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local out = {}&lt;br /&gt;
	for key, item in pairs(value) do&lt;br /&gt;
		if type(key) ~= &amp;#039;string&amp;#039; or key:sub(1, 1) ~= &amp;#039;_&amp;#039; then&lt;br /&gt;
			out[key] = toPlain(item)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return out&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- @return table&lt;br /&gt;
local function load()&lt;br /&gt;
	if not data then&lt;br /&gt;
		-- loadJsonData RAISES on a missing page and on invalid JSON. The second&lt;br /&gt;
		-- should be impossible, since MediaWiki validates JSON at save, but the&lt;br /&gt;
		-- page can still be moved or deleted — and that must cost the page its&lt;br /&gt;
		-- settings rather than its render.&lt;br /&gt;
		local ok, loaded = pcall(mw.loadJsonData, DATA_PAGE)&lt;br /&gt;
		data = ok and toPlain(loaded) or {}&lt;br /&gt;
	end&lt;br /&gt;
	return data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- One top-level section of the settings, always a table.&lt;br /&gt;
---&lt;br /&gt;
--- Returns an empty table for a section that is missing or blank, so a caller&lt;br /&gt;
--- can read a field off it without testing first: an absent section and a&lt;br /&gt;
--- section with nothing set behave identically, which is what makes &amp;quot;clear the&lt;br /&gt;
--- value to remove the thing&amp;quot; work.&lt;br /&gt;
---&lt;br /&gt;
--- @param name string&lt;br /&gt;
--- @return table&lt;br /&gt;
function p.section(name)&lt;br /&gt;
	local value = load()[name]&lt;br /&gt;
	return type(value) == &amp;#039;table&amp;#039; and value or {}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- One top-level list, always an array.&lt;br /&gt;
---&lt;br /&gt;
--- @param name string&lt;br /&gt;
--- @return table[]&lt;br /&gt;
function p.list(name)&lt;br /&gt;
	local value = load()[name]&lt;br /&gt;
	if type(value) ~= &amp;#039;table&amp;#039; or value[1] == nil then&lt;br /&gt;
		return {}&lt;br /&gt;
	end&lt;br /&gt;
	return value&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- Turns a settings time into ISO 8601 UTC, or nil.&lt;br /&gt;
---&lt;br /&gt;
--- Accepts `YYYY-MM-DD`, optionally followed by `HH:MM` or `HH:MM:SS` with&lt;br /&gt;
--- either a space or a `T` between them, and tolerates a trailing `UTC` or `Z`.&lt;br /&gt;
--- Anything else returns nil.&lt;br /&gt;
---&lt;br /&gt;
--- The strictness is the point. The settings file is edited by hand, so it will&lt;br /&gt;
--- receive free-form dates and placeholders like &amp;quot;TBA&amp;quot;. Module:Countdown RAISES&lt;br /&gt;
--- on a date it cannot read, and a raise takes down the whole page — so an&lt;br /&gt;
--- unreadable date has to cost the clock and nothing else.&lt;br /&gt;
---&lt;br /&gt;
--- Here rather than in the card that draws the clock, because both event cards&lt;br /&gt;
--- read the same two keys and neither should own the rule for what they accept.&lt;br /&gt;
---&lt;br /&gt;
--- @param value string|nil&lt;br /&gt;
--- @return string|nil&lt;br /&gt;
function p.toIso(value)&lt;br /&gt;
	if not value then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local raw = mw.text.trim(value)&lt;br /&gt;
	raw = (raw:gsub(&amp;#039;%s*[Uu][Tt][Cc]$&amp;#039;, &amp;#039;&amp;#039;))&lt;br /&gt;
	raw = (raw:gsub(&amp;#039;%s*[Zz]$&amp;#039;, &amp;#039;&amp;#039;))&lt;br /&gt;
&lt;br /&gt;
	local date, time = mw.text.trim(raw):match(&amp;#039;^(%d%d%d%d%-%d%d%-%d%d)[T ]?(.*)$&amp;#039;)&lt;br /&gt;
	if not date then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	time = mw.text.trim(time)&lt;br /&gt;
	if time == &amp;#039;&amp;#039; then&lt;br /&gt;
		return date .. &amp;#039;T00:00:00Z&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local hour, minute, second = time:match(&amp;#039;^(%d%d):(%d%d):(%d%d)$&amp;#039;)&lt;br /&gt;
	if not hour then&lt;br /&gt;
		hour, minute = time:match(&amp;#039;^(%d%d):(%d%d)$&amp;#039;)&lt;br /&gt;
		second = &amp;#039;00&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	if not hour then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return string.format(&amp;#039;%sT%s:%s:%sZ&amp;#039;, date, hour, minute, second)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--- The build the wiki is running: the first patch whose channel is LIVE, or —&lt;br /&gt;
--- when an editor has not marked one — simply the first with a name.&lt;br /&gt;
---&lt;br /&gt;
--- Resolved HERE and not in each consumer, because two of them ask: the chip&lt;br /&gt;
--- that gets the filled marker and the card that says &amp;quot;New in …&amp;quot; have to name&lt;br /&gt;
--- the same build, and they would drift the moment either grew its own rule.&lt;br /&gt;
---&lt;br /&gt;
--- The fallback is deliberate. Forgetting `&amp;quot;channel&amp;quot;: &amp;quot;LIVE&amp;quot;` should cost the&lt;br /&gt;
--- filled marker, not the patch card — a silently missing card is the harder&lt;br /&gt;
--- failure to notice.&lt;br /&gt;
---&lt;br /&gt;
--- @return table|nil&lt;br /&gt;
function p.livePatch()&lt;br /&gt;
	local patches = p.list(&amp;#039;patches&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
	for _, patch in ipairs(patches) do&lt;br /&gt;
		if patch.name and patch.channel and mw.ustring.upper(patch.channel) == &amp;#039;LIVE&amp;#039; then&lt;br /&gt;
			return patch&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	for _, patch in ipairs(patches) do&lt;br /&gt;
		if patch.name then&lt;br /&gt;
			return patch&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Luxbane</name></author>
	</entry>
</feed>