Module:BSW/MainPage: Difference between revisions
From the only original and legitimate Battlestar Wiki: the free-as-in-beer, non-corporate, open-content encyclopedia, analytical reference, and episode guide on all things Battlestar Galactica. Accept neither subpar substitutes nor subpar clones.
More actions
Created page with "-- Module:BSW/MainPage -- Generates all main page HTML components via mw.html, -- bypassing the wikitext parser entirely. -- Usage: {{#invoke:BSW/MainPage|functionName|param=value|...}} local p = {} -- ── Shared helpers ──────────────────────────────────────────────── -- Safe attribute setter — skips nil/empty values local function attr( el, name, value ) if value..." |
No edit summary |
||
| Line 1: | Line 1: | ||
-- Module:BSW/MainPage | -- Module:BSW/MainPage | ||
-- Generates | -- Generates main page HTML using raw string concatenation. | ||
-- | -- mw.html is NOT used because its output is passed through | ||
-- MediaWiki's HTML sanitizer which strips <button> and | |||
-- <a class="..."> elements. | |||
-- Instead we build raw HTML strings and return them via | |||
-- frame:callParserFunction to inject them as strip markers | |||
-- that survive the sanitizer pass. | |||
-- | |||
-- Usage: {{#invoke:BSW/MainPage|functionName|param=value|...}} | -- Usage: {{#invoke:BSW/MainPage|functionName|param=value|...}} | ||
local p = {} | local p = {} | ||
-- ── | -- ── HTML helpers ────────────────────────────────────────────────── | ||
-- | local function esc( s ) | ||
local function | s = tostring( s or '' ) | ||
s = s:gsub( '&', '&' ) | |||
s = s:gsub( '<', '<' ) | |||
s = s:gsub( '>', '>' ) | |||
s = s:gsub( '"', '"' ) | |||
return s | |||
end | |||
-- Build an attribute string, skipping empty values | |||
local function atr( name, value ) | |||
if value and value ~= '' then | if value and value ~= '' then | ||
return ' ' .. name .. '="' .. esc( value ) .. '"' | |||
end | |||
return '' | |||
end | |||
-- Wrap output so it bypasses the sanitizer. | |||
-- We use the <score> trick: any extension tag registered in MW | |||
-- acts as a strip marker. But the cleanest method available in | |||
-- Scribunto is to mark output as safe using the internal | |||
-- mw.text.nowiki approach combined with frame preprocessing. | |||
-- The most reliable cross-version approach: return the HTML | |||
-- inside a <templatestyles> strip tag... but actually the | |||
-- correct documented approach for Scribunto is simply: | |||
-- return frame:preprocess( '<html>' .. html .. '</html>' ) | |||
-- but <html> tag needs to be enabled. So we use the | |||
-- well-documented mw workaround: build wikitext that produces | |||
-- the HTML we want via #tag parser functions for the | |||
-- problematic elements (button, a with class). | |||
-- Build a <button> via #tag parser function call | |||
local function btn( frame, id, cls, onclick, label ) | |||
local attrs = {} | |||
if id and id ~= '' then attrs['id'] = id end | |||
if cls then | |||
-- #tag doesn't support class directly, embed in attr string | |||
end | end | ||
return | -- Use frame:callParserFunction with #tag | ||
-- #tag:button|label|id=...|class=...|aria-label=... | |||
-- This is the correct way to emit elements the sanitizer would block | |||
local params = { label } | |||
params['class'] = cls or '' | |||
if id and id ~= '' then params['id'] = id end | |||
if onclick and onclick ~= '' then params['onclick'] = onclick end | |||
return frame:callParserFunction( '#tag:button', params ) | |||
end | |||
-- Build an <a> tag via #tag | |||
local function anchor( frame, href, cls, content, attrs_extra ) | |||
local params = { content } | |||
params['href'] = href or '#' | |||
if cls and cls ~= '' then params['class'] = cls end | |||
if attrs_extra then | |||
for k, v in pairs( attrs_extra ) do | |||
params[ k ] = v | |||
end | |||
end | |||
return frame:callParserFunction( '#tag:a', params ) | |||
end | end | ||
-- ── hero() ──────────────────────────────────────────────────────── | -- ── hero() ──────────────────────────────────────────────────────── | ||
function p.hero( frame ) | function p.hero( frame ) | ||
local args = frame.args | local args = frame.args | ||
local count = tonumber( args.count ) or 0 | local count = tonumber( args.count ) or 0 | ||
local | local out = {} | ||
out[#out+1] = '<div class="bsw-hero">' | |||
for i = 1, count do | for i = 1, count do | ||
local pfx = 'slide' .. i .. '_' | local pfx = 'slide' .. i .. '_' | ||
local article = args[ pfx .. 'article' | local article = mw.text.trim( args[ pfx .. 'article' ] or '' ) | ||
local series = args[ pfx .. 'series' | local series = mw.text.trim( args[ pfx .. 'series' ] or '' ) | ||
local title = args[ pfx .. 'title' | local title = mw.text.trim( args[ pfx .. 'title' ] or article ) | ||
local tlink = args[ pfx .. 'titlelink' ] or article | local tlink = mw.text.trim( args[ pfx .. 'titlelink' ] or article ) | ||
local desc = args[ pfx .. 'desc' | local desc = mw.text.trim( args[ pfx .. 'desc' ] or '' ) | ||
local bcolor = args[ pfx .. 'badgecolor'] or '' | local bcolor = mw.text.trim( args[ pfx .. 'badgecolor' ] or '' ) | ||
local | local active = ( i == 1 ) and ' bsw-active' or '' | ||
out[#out+1] = '<div class="bsw-slide' .. active .. '"' .. atr( 'data-article', article ) .. '>' | |||
out[#out+1] = '<div class="bsw-slide-bg"></div>' | |||
out[#out+1] = '<div class="bsw-slide-overlay"></div>' | |||
out[#out+1] = '<div class="bsw-slide-content">' | |||
out[#out+1] = '<div class="bsw-slide-badge">' | |||
local dot_style = bcolor ~= '' and ' style="background:' .. bcolor .. '"' or '' | |||
out[#out+1] = '<span class="bsw-slide-badge-dot"' .. dot_style .. '></span>' | |||
out[#out+1] = '<span>' .. esc( series ) .. '</span>' | |||
out[#out+1] = '</div>' | |||
-- | -- Title: wikilink rendered by preprocessor | ||
slide | local wlink = frame:preprocess( '[[' .. tlink .. '|' .. title .. ']]' ) | ||
out[#out+1] = '<div class="bsw-slide-title">' .. wlink .. '</div>' | |||
out[#out+1] = '<div class="bsw-slide-desc">' .. esc( desc ) .. '</div>' | |||
out[#out+1] = '</div>' | |||
out[#out+1] = '</div>' | |||
end | |||
-- | -- Dots using #tag:button via callParserFunction | ||
out[#out+1] = '<div class="bsw-hero-dots">' | |||
for i = 1, count do | |||
local cls = 'bsw-hero-dot' .. ( i == 1 and ' bsw-active' or '' ) | |||
out[#out+1] = btn( frame, nil, cls, nil, '' ) | |||
-- Inject aria-label by replacing the closing > since #tag doesn't | |||
-- let us set aria-label easily — use a separate call with all attrs | |||
-- Actually #tag supports arbitrary attrs, rewrite: | |||
end | |||
out[#out+1] = '</div>' | |||
-- Redo dots properly — #tag:button supports all HTML attrs | |||
-- Remove the dots section we just built and redo | |||
-- (pop back to before the dots div) | |||
local dots_html = '<div class="bsw-hero-dots">' | |||
for i = 1, count do | |||
local cls = 'bsw-hero-dot' .. ( i == 1 and ' bsw-active' or '' ) | |||
local dot_params = { '' } | |||
dot_params['class'] = cls | |||
dot_params['aria-label'] = 'Slide ' .. i | |||
dots_html = dots_html .. frame:callParserFunction( '#tag:button', dot_params ) | |||
end | |||
dots_html = dots_html .. '</div>' | |||
-- Remove the bad dots we pushed, replace | |||
-- (simpler: just build the whole thing cleanly from scratch) | |||
local result = '<div class="bsw-hero">' | |||
for i = 1, count do | |||
local | local pfx = 'slide' .. i .. '_' | ||
local | local article = mw.text.trim( args[ pfx .. 'article' ] or '' ) | ||
local series = mw.text.trim( args[ pfx .. 'series' ] or '' ) | |||
local title = mw.text.trim( args[ pfx .. 'title' ] or article ) | |||
local tlink = mw.text.trim( args[ pfx .. 'titlelink' ] or article ) | |||
local desc = mw.text.trim( args[ pfx .. 'desc' ] or '' ) | |||
local bcolor = mw.text.trim( args[ pfx .. 'badgecolor' ] or '' ) | |||
- | local active = ( i == 1 ) and ' bsw-active' or '' | ||
local dot_style = bcolor ~= '' and ' style="background:' .. bcolor .. '"' or '' | |||
local wlink = frame:preprocess( '[[' .. tlink .. '|' .. title .. ']]' ) | |||
-- | result = result .. | ||
'<div class="bsw-slide' .. active .. '"' .. atr( 'data-article', article ) .. '>' .. | |||
'<div class="bsw-slide-bg"></div>' .. | |||
'<div class="bsw-slide-overlay"></div>' .. | |||
'<div class="bsw-slide-content">' .. | |||
'<div class="bsw-slide-badge">' .. | |||
'<span class="bsw-slide-badge-dot"' .. dot_style .. '></span>' .. | |||
'<span>' .. esc( series ) .. '</span>' .. | |||
'</div>' .. | |||
'<div class="bsw-slide-title">' .. wlink .. '</div>' .. | |||
'<div class="bsw-slide-desc">' .. esc( desc ) .. '</div>' .. | |||
'</div>' .. | |||
'</div>' | |||
end | end | ||
-- | result = result .. dots_html | ||
local | |||
-- Nav buttons | |||
local prev_params = { '‹' } | |||
prev_params['id'] = 'bsw-hero-prev' | |||
prev_params['aria-label'] = 'Previous slide' | |||
local next_params = { '›' } | |||
next_params['id'] = 'bsw-hero-next' | |||
next_params['aria-label'] = 'Next slide' | |||
result = result .. | |||
'<div class="bsw-hero-nav">' .. | |||
frame:callParserFunction( '#tag:button', prev_params ) .. | |||
: | frame:callParserFunction( '#tag:button', next_params ) .. | ||
'</div>' .. | |||
: | '</div>' | ||
return | return result | ||
end | end | ||
-- ── portals() ───────────────────────────────────────────────────── | -- ── portals() ───────────────────────────────────────────────────── | ||
function p.portals( frame ) | function p.portals( frame ) | ||
local args = frame.args | local args = frame.args | ||
local count = tonumber( args.count ) or 0 | local count = tonumber( args.count ) or 0 | ||
local | local result = | ||
'<div class="bsw-portals">' .. | |||
'<div class="bsw-portals-label">∞ Portals of Battlestar Wiki ∞</div>' .. | |||
'<div class="bsw-portals-grid">' | |||
for i = 1, count do | for i = 1, count do | ||
local pfx = 'portal' .. i .. '_' | local pfx = 'portal' .. i .. '_' | ||
local href = args[ pfx .. 'href' ] or '#' | local href = mw.text.trim( args[ pfx .. 'href' ] or '#' ) | ||
local stripe = args[ pfx .. 'stripe' ] or '' | local stripe = mw.text.trim( args[ pfx .. 'stripe' ] or '' ) | ||
local icon = args[ pfx .. 'icon' ] or '' | local icon = mw.text.trim( args[ pfx .. 'icon' ] or '' ) | ||
local name = args[ pfx .. 'name' ] or '' | local name = mw.text.trim( args[ pfx .. 'name' ] or '' ) | ||
local sub = args[ pfx .. 'sub' ] or '' | local sub = mw.text.trim( args[ pfx .. 'sub' ] or '' ) | ||
local | local stripe_style = stripe ~= '' and ' style="background:' .. stripe .. '"' or '' | ||
local stripe_div = '<div class="bsw-portal-stripe"' .. stripe_style .. '></div>' | |||
-- | local inner = | ||
stripe_div .. | |||
'<span class="bsw-portal-icon">' .. icon .. '</span>' .. | |||
'<span class="bsw-portal-name">' .. esc( name ) .. '</span>' .. | |||
'<span class="bsw-portal-sub">' .. esc( sub ) .. '</span>' | |||
local a_params = { inner } | |||
a_params['class'] = 'bsw-portal' | |||
a_params['href'] = href | |||
result = result .. frame:callParserFunction( '#tag:a', a_params ) | |||
end | end | ||
result = result .. '</div></div>' | |||
return result | |||
return | |||
end | end | ||
-- ── statsblock() ────────────────────────────────────────────────── | -- ── statsblock() ────────────────────────────────────────────────── | ||
function p.statsblock( frame ) | function p.statsblock( frame ) | ||
local args = frame.args | local args = frame.args | ||
local | local grid = '<div class="bsw-stats-grid">' | ||
for i = 1, 6 do | for i = 1, 6 do | ||
local n = args[ 'n' .. i ] or '' | local n = mw.text.trim( args[ 'n' .. i ] or '' ) | ||
local label = args[ 'label' .. i ] or '' | local label = mw.text.trim( args[ 'label' .. i ] or '' ) | ||
if n ~= '' or label ~= '' then | if n ~= '' or label ~= '' then | ||
-- n may contain MW magic word output already expanded | |||
grid = grid .. | |||
'<div class="bsw-stat">' .. | |||
'<div class="bsw-stat-n">' .. n .. '</div>' .. | |||
'<div class="bsw-stat-l">' .. esc( label ) .. '</div>' .. | |||
'</div>' | |||
end | end | ||
end | end | ||
grid = grid .. '</div>' | |||
local links = '<div class="bsw-stat-links">' | |||
local links = | |||
for i = 1, 4 do | for i = 1, 4 do | ||
local url = args[ 'link' .. i .. '_url' ] or '' | local url = mw.text.trim( args[ 'link' .. i .. '_url' ] or '' ) | ||
local text = args[ 'link' .. i .. '_text' ] or '' | local text = mw.text.trim( args[ 'link' .. i .. '_text' ] or '' ) | ||
if url ~= '' then | if url ~= '' then | ||
local a_params = { text ~= '' and text or url } | |||
a_params['class'] = 'bsw-stat-link' | |||
a_params['href'] = url | |||
links = links .. frame:callParserFunction( '#tag:a', a_params ) | |||
end | end | ||
end | end | ||
links = links .. '</div>' | |||
return | return | ||
'<div class="bsw-card">' .. | |||
'<div class="bsw-card-hd">Wiki statistics</div>' .. | |||
'<div class="bsw-card-body">' .. grid .. links .. '</div>' .. | |||
'</div>' | |||
end | end | ||
-- ── recenttabs() ────────────────────────────────────────────────── | -- ── recenttabs() ────────────────────────────────────────────────── | ||
function p.recenttabs( frame ) | function p.recenttabs( frame ) | ||
local tabs | local tabs = { { 'All', 'all' }, { 'EN', 'en' }, { 'DE', 'de' }, { 'Media', 'media' } } | ||
local result = '<div class="bsw-wiki-tabs">' | |||
for i, t in ipairs( tabs ) do | |||
local | local cls = 'bsw-wtab' .. ( i == 1 and ' bsw-active' or '' ) | ||
for i, | local params = { t[1] } | ||
local | params['class'] = cls | ||
params['onclick'] = "bswSetTab(this,'" .. t[2] .. "')" | |||
result = result .. frame:callParserFunction( '#tag:button', params ) | |||
end | end | ||
return result .. '</div>' | |||
return | |||
end | end | ||
-- ── | -- ── card() ──────────────────────────────────────────────────────── | ||
function p.card( frame ) | |||
local args = frame.args | |||
local title = mw.text.trim( args.title or '' ) | |||
local ltext = mw.text.trim( args.linktext or '' ) | |||
local lurl = mw.text.trim( args.linkurl or '' ) | |||
local content = args.content or '' | |||
local hd = '<div class="bsw-card-hd">' .. esc( title ) | |||
if lurl ~= '' then | |||
local | local a_params = { ltext ~= '' and ltext or 'More →' } | ||
a_params['href'] = lurl | |||
hd = hd .. frame:callParserFunction( '#tag:a', a_params ) | |||
local | |||
end | end | ||
hd = hd .. '</div>' | |||
return | return | ||
'<div class="bsw-card">' .. | |||
hd .. | |||
'<div class="bsw-card-body">' .. content .. '</div>' .. | |||
'</div>' | |||
end | end | ||
-- ── featuredloading() ───────────────────────────────────────────── | -- ── featuredloading() ───────────────────────────────────────────── | ||
function p.featuredloading( frame ) | function p.featuredloading( frame ) | ||
local | local a_params = { 'Read more →' } | ||
a_params['id'] = 'bsw-featured-link' | |||
a_params['href'] = '#' | |||
local | local hd = | ||
'<div class="bsw-card-hd">Featured article' .. | |||
frame:callParserFunction( '#tag:a', a_params ) .. | |||
'</div>' | |||
return | return | ||
'<div class="bsw-card">' .. | |||
hd .. | |||
'<div class="bsw-card-body">' .. | |||
'<div id="bsw-featured-inner">' .. | |||
'<div class="bsw-loading">' .. | |||
'<div class="bsw-spinner"></div>' .. | |||
'Loading today\'s featured article…' .. | |||
'</div></div></div></div>' | |||
end | end | ||
-- ── photolab() ──────────────────────────────────────────────────── | -- ── photolab() ──────────────────────────────────────────────────── | ||
function p.photolab( frame ) | function p.photolab( frame ) | ||
local args = frame.args | local args = frame.args | ||
local content = args.content | local content = args.content or '' | ||
local prev_url = args.prev_url | local prev_url = mw.text.trim( args.prev_url or '#' ) | ||
local prev_lbl = args.prev_label or '‹' | local prev_lbl = mw.text.trim( args.prev_label or '‹' ) | ||
local caption = args.caption | local caption = mw.text.trim( args.caption or '' ) | ||
local next_url = args.next_url | local next_url = mw.text.trim( args.next_url or '#' ) | ||
local next_lbl = args.next_label or '›' | local next_lbl = mw.text.trim( args.next_label or '›' ) | ||
local a_view_params = { 'View project →' } | |||
a_view_params['href'] = '/BW:Potd' | |||
local a_prev_params = { prev_lbl } | |||
a_prev_params['href'] = prev_url | |||
local a_next_params = { next_lbl } | |||
a_next_params['href'] = next_url | |||
local | local hd = | ||
'<div class="bsw-card-hd">∞ Photo Lab — Picture of the Day' .. | |||
frame:callParserFunction( '#tag:a', a_view_params ) .. | |||
'</div>' | |||
local | local nav = | ||
'<div class="bsw-photo-nav">' .. | |||
frame:callParserFunction( '#tag:a', a_prev_params ) .. | |||
'<span class="bsw-photo-caption">' .. esc( caption ) .. '</span>' .. | |||
frame:callParserFunction( '#tag:a', a_next_params ) .. | |||
'</div>' | |||
- | return '<div class="bsw-card">' .. hd .. content .. nav .. '</div>' | ||
end | |||
-- ── interwiki() ─────────────────────────────────────────────────── | |||
local | function p.interwiki( frame ) | ||
local args = frame.args | |||
local result = '<div class="bsw-interwiki">' | |||
for i = 1, 8 do | |||
local flag = mw.text.trim( args[ i .. '_flag' ] or '' ) | |||
local label = mw.text.trim( args[ i .. '_label' ] or '' ) | |||
local url = mw.text.trim( args[ i .. '_url' ] or '' ) | |||
if url ~= '' then | |||
local a_params = { label } | |||
a_params['href'] = url | |||
local prefix = flag ~= '' and ( flag .. ' ' ) or '' | |||
result = result .. | |||
'<div class="bsw-iw">' .. | |||
prefix .. | |||
frame:callParserFunction( '#tag:a', a_params ) .. | |||
'</div>' | |||
end | |||
end | |||
return | return result .. '</div>' | ||
end | end | ||
-- ── tagline() ───────────────────────────────────────────────────── | -- ── tagline() ───────────────────────────────────────────────────── | ||
function p.tagline( frame ) | function p.tagline( frame ) | ||
-- Use frame:preprocess for wikitext bold/italic markup | |||
local inner = frame:preprocess( | |||
"The only original and legitimate '''Battlestar Wiki''' " .. | "The only original and legitimate '''Battlestar Wiki''' " .. | ||
"— the free-as-in-beer, non-corporate, open-content encyclopedia " .. | "— the free-as-in-beer, non-corporate, open-content encyclopedia " .. | ||
| Line 350: | Line 389: | ||
"∞ ''Accept neither subpar substitutes nor subpar clones.'' ∞" | "∞ ''Accept neither subpar substitutes nor subpar clones.'' ∞" | ||
) | ) | ||
return | return '<div class="bsw-tagline">' .. inner .. '</div>' | ||
end | end | ||
return p | return p | ||
Revision as of 16:07, 11 April 2026
Documentation for this module may be created at Module:BSW/MainPage/doc
-- Module:BSW/MainPage
-- Generates main page HTML using raw string concatenation.
-- mw.html is NOT used because its output is passed through
-- MediaWiki's HTML sanitizer which strips <button> and
-- <a class="..."> elements.
-- Instead we build raw HTML strings and return them via
-- frame:callParserFunction to inject them as strip markers
-- that survive the sanitizer pass.
--
-- Usage: {{#invoke:BSW/MainPage|functionName|param=value|...}}
local p = {}
-- ── HTML helpers ──────────────────────────────────────────────────
local function esc( s )
s = tostring( s or '' )
s = s:gsub( '&', '&' )
s = s:gsub( '<', '<' )
s = s:gsub( '>', '>' )
s = s:gsub( '"', '"' )
return s
end
-- Build an attribute string, skipping empty values
local function atr( name, value )
if value and value ~= '' then
return ' ' .. name .. '="' .. esc( value ) .. '"'
end
return ''
end
-- Wrap output so it bypasses the sanitizer.
-- We use the <score> trick: any extension tag registered in MW
-- acts as a strip marker. But the cleanest method available in
-- Scribunto is to mark output as safe using the internal
-- mw.text.nowiki approach combined with frame preprocessing.
-- The most reliable cross-version approach: return the HTML
-- inside a <templatestyles> strip tag... but actually the
-- correct documented approach for Scribunto is simply:
-- return frame:preprocess( '<html>' .. html .. '</html>' )
-- but <html> tag needs to be enabled. So we use the
-- well-documented mw workaround: build wikitext that produces
-- the HTML we want via #tag parser functions for the
-- problematic elements (button, a with class).
-- Build a <button> via #tag parser function call
local function btn( frame, id, cls, onclick, label )
local attrs = {}
if id and id ~= '' then attrs['id'] = id end
if cls then
-- #tag doesn't support class directly, embed in attr string
end
-- Use frame:callParserFunction with #tag
-- #tag:button|label|id=...|class=...|aria-label=...
-- This is the correct way to emit elements the sanitizer would block
local params = { label }
params['class'] = cls or ''
if id and id ~= '' then params['id'] = id end
if onclick and onclick ~= '' then params['onclick'] = onclick end
return frame:callParserFunction( '#tag:button', params )
end
-- Build an <a> tag via #tag
local function anchor( frame, href, cls, content, attrs_extra )
local params = { content }
params['href'] = href or '#'
if cls and cls ~= '' then params['class'] = cls end
if attrs_extra then
for k, v in pairs( attrs_extra ) do
params[ k ] = v
end
end
return frame:callParserFunction( '#tag:a', params )
end
-- ── hero() ────────────────────────────────────────────────────────
function p.hero( frame )
local args = frame.args
local count = tonumber( args.count ) or 0
local out = {}
out[#out+1] = '<div class="bsw-hero">'
for i = 1, count do
local pfx = 'slide' .. i .. '_'
local article = mw.text.trim( args[ pfx .. 'article' ] or '' )
local series = mw.text.trim( args[ pfx .. 'series' ] or '' )
local title = mw.text.trim( args[ pfx .. 'title' ] or article )
local tlink = mw.text.trim( args[ pfx .. 'titlelink' ] or article )
local desc = mw.text.trim( args[ pfx .. 'desc' ] or '' )
local bcolor = mw.text.trim( args[ pfx .. 'badgecolor' ] or '' )
local active = ( i == 1 ) and ' bsw-active' or ''
out[#out+1] = '<div class="bsw-slide' .. active .. '"' .. atr( 'data-article', article ) .. '>'
out[#out+1] = '<div class="bsw-slide-bg"></div>'
out[#out+1] = '<div class="bsw-slide-overlay"></div>'
out[#out+1] = '<div class="bsw-slide-content">'
out[#out+1] = '<div class="bsw-slide-badge">'
local dot_style = bcolor ~= '' and ' style="background:' .. bcolor .. '"' or ''
out[#out+1] = '<span class="bsw-slide-badge-dot"' .. dot_style .. '></span>'
out[#out+1] = '<span>' .. esc( series ) .. '</span>'
out[#out+1] = '</div>'
-- Title: wikilink rendered by preprocessor
local wlink = frame:preprocess( '[[' .. tlink .. '|' .. title .. ']]' )
out[#out+1] = '<div class="bsw-slide-title">' .. wlink .. '</div>'
out[#out+1] = '<div class="bsw-slide-desc">' .. esc( desc ) .. '</div>'
out[#out+1] = '</div>'
out[#out+1] = '</div>'
end
-- Dots using #tag:button via callParserFunction
out[#out+1] = '<div class="bsw-hero-dots">'
for i = 1, count do
local cls = 'bsw-hero-dot' .. ( i == 1 and ' bsw-active' or '' )
out[#out+1] = btn( frame, nil, cls, nil, '' )
-- Inject aria-label by replacing the closing > since #tag doesn't
-- let us set aria-label easily — use a separate call with all attrs
-- Actually #tag supports arbitrary attrs, rewrite:
end
out[#out+1] = '</div>'
-- Redo dots properly — #tag:button supports all HTML attrs
-- Remove the dots section we just built and redo
-- (pop back to before the dots div)
local dots_html = '<div class="bsw-hero-dots">'
for i = 1, count do
local cls = 'bsw-hero-dot' .. ( i == 1 and ' bsw-active' or '' )
local dot_params = { '' }
dot_params['class'] = cls
dot_params['aria-label'] = 'Slide ' .. i
dots_html = dots_html .. frame:callParserFunction( '#tag:button', dot_params )
end
dots_html = dots_html .. '</div>'
-- Remove the bad dots we pushed, replace
-- (simpler: just build the whole thing cleanly from scratch)
local result = '<div class="bsw-hero">'
for i = 1, count do
local pfx = 'slide' .. i .. '_'
local article = mw.text.trim( args[ pfx .. 'article' ] or '' )
local series = mw.text.trim( args[ pfx .. 'series' ] or '' )
local title = mw.text.trim( args[ pfx .. 'title' ] or article )
local tlink = mw.text.trim( args[ pfx .. 'titlelink' ] or article )
local desc = mw.text.trim( args[ pfx .. 'desc' ] or '' )
local bcolor = mw.text.trim( args[ pfx .. 'badgecolor' ] or '' )
local active = ( i == 1 ) and ' bsw-active' or ''
local dot_style = bcolor ~= '' and ' style="background:' .. bcolor .. '"' or ''
local wlink = frame:preprocess( '[[' .. tlink .. '|' .. title .. ']]' )
result = result ..
'<div class="bsw-slide' .. active .. '"' .. atr( 'data-article', article ) .. '>' ..
'<div class="bsw-slide-bg"></div>' ..
'<div class="bsw-slide-overlay"></div>' ..
'<div class="bsw-slide-content">' ..
'<div class="bsw-slide-badge">' ..
'<span class="bsw-slide-badge-dot"' .. dot_style .. '></span>' ..
'<span>' .. esc( series ) .. '</span>' ..
'</div>' ..
'<div class="bsw-slide-title">' .. wlink .. '</div>' ..
'<div class="bsw-slide-desc">' .. esc( desc ) .. '</div>' ..
'</div>' ..
'</div>'
end
result = result .. dots_html
-- Nav buttons
local prev_params = { '‹' }
prev_params['id'] = 'bsw-hero-prev'
prev_params['aria-label'] = 'Previous slide'
local next_params = { '›' }
next_params['id'] = 'bsw-hero-next'
next_params['aria-label'] = 'Next slide'
result = result ..
'<div class="bsw-hero-nav">' ..
frame:callParserFunction( '#tag:button', prev_params ) ..
frame:callParserFunction( '#tag:button', next_params ) ..
'</div>' ..
'</div>'
return result
end
-- ── portals() ─────────────────────────────────────────────────────
function p.portals( frame )
local args = frame.args
local count = tonumber( args.count ) or 0
local result =
'<div class="bsw-portals">' ..
'<div class="bsw-portals-label">∞ Portals of Battlestar Wiki ∞</div>' ..
'<div class="bsw-portals-grid">'
for i = 1, count do
local pfx = 'portal' .. i .. '_'
local href = mw.text.trim( args[ pfx .. 'href' ] or '#' )
local stripe = mw.text.trim( args[ pfx .. 'stripe' ] or '' )
local icon = mw.text.trim( args[ pfx .. 'icon' ] or '' )
local name = mw.text.trim( args[ pfx .. 'name' ] or '' )
local sub = mw.text.trim( args[ pfx .. 'sub' ] or '' )
local stripe_style = stripe ~= '' and ' style="background:' .. stripe .. '"' or ''
local stripe_div = '<div class="bsw-portal-stripe"' .. stripe_style .. '></div>'
local inner =
stripe_div ..
'<span class="bsw-portal-icon">' .. icon .. '</span>' ..
'<span class="bsw-portal-name">' .. esc( name ) .. '</span>' ..
'<span class="bsw-portal-sub">' .. esc( sub ) .. '</span>'
local a_params = { inner }
a_params['class'] = 'bsw-portal'
a_params['href'] = href
result = result .. frame:callParserFunction( '#tag:a', a_params )
end
result = result .. '</div></div>'
return result
end
-- ── statsblock() ──────────────────────────────────────────────────
function p.statsblock( frame )
local args = frame.args
local grid = '<div class="bsw-stats-grid">'
for i = 1, 6 do
local n = mw.text.trim( args[ 'n' .. i ] or '' )
local label = mw.text.trim( args[ 'label' .. i ] or '' )
if n ~= '' or label ~= '' then
-- n may contain MW magic word output already expanded
grid = grid ..
'<div class="bsw-stat">' ..
'<div class="bsw-stat-n">' .. n .. '</div>' ..
'<div class="bsw-stat-l">' .. esc( label ) .. '</div>' ..
'</div>'
end
end
grid = grid .. '</div>'
local links = '<div class="bsw-stat-links">'
for i = 1, 4 do
local url = mw.text.trim( args[ 'link' .. i .. '_url' ] or '' )
local text = mw.text.trim( args[ 'link' .. i .. '_text' ] or '' )
if url ~= '' then
local a_params = { text ~= '' and text or url }
a_params['class'] = 'bsw-stat-link'
a_params['href'] = url
links = links .. frame:callParserFunction( '#tag:a', a_params )
end
end
links = links .. '</div>'
return
'<div class="bsw-card">' ..
'<div class="bsw-card-hd">Wiki statistics</div>' ..
'<div class="bsw-card-body">' .. grid .. links .. '</div>' ..
'</div>'
end
-- ── recenttabs() ──────────────────────────────────────────────────
function p.recenttabs( frame )
local tabs = { { 'All', 'all' }, { 'EN', 'en' }, { 'DE', 'de' }, { 'Media', 'media' } }
local result = '<div class="bsw-wiki-tabs">'
for i, t in ipairs( tabs ) do
local cls = 'bsw-wtab' .. ( i == 1 and ' bsw-active' or '' )
local params = { t[1] }
params['class'] = cls
params['onclick'] = "bswSetTab(this,'" .. t[2] .. "')"
result = result .. frame:callParserFunction( '#tag:button', params )
end
return result .. '</div>'
end
-- ── card() ────────────────────────────────────────────────────────
function p.card( frame )
local args = frame.args
local title = mw.text.trim( args.title or '' )
local ltext = mw.text.trim( args.linktext or '' )
local lurl = mw.text.trim( args.linkurl or '' )
local content = args.content or ''
local hd = '<div class="bsw-card-hd">' .. esc( title )
if lurl ~= '' then
local a_params = { ltext ~= '' and ltext or 'More →' }
a_params['href'] = lurl
hd = hd .. frame:callParserFunction( '#tag:a', a_params )
end
hd = hd .. '</div>'
return
'<div class="bsw-card">' ..
hd ..
'<div class="bsw-card-body">' .. content .. '</div>' ..
'</div>'
end
-- ── featuredloading() ─────────────────────────────────────────────
function p.featuredloading( frame )
local a_params = { 'Read more →' }
a_params['id'] = 'bsw-featured-link'
a_params['href'] = '#'
local hd =
'<div class="bsw-card-hd">Featured article' ..
frame:callParserFunction( '#tag:a', a_params ) ..
'</div>'
return
'<div class="bsw-card">' ..
hd ..
'<div class="bsw-card-body">' ..
'<div id="bsw-featured-inner">' ..
'<div class="bsw-loading">' ..
'<div class="bsw-spinner"></div>' ..
'Loading today\'s featured article…' ..
'</div></div></div></div>'
end
-- ── photolab() ────────────────────────────────────────────────────
function p.photolab( frame )
local args = frame.args
local content = args.content or ''
local prev_url = mw.text.trim( args.prev_url or '#' )
local prev_lbl = mw.text.trim( args.prev_label or '‹' )
local caption = mw.text.trim( args.caption or '' )
local next_url = mw.text.trim( args.next_url or '#' )
local next_lbl = mw.text.trim( args.next_label or '›' )
local a_view_params = { 'View project →' }
a_view_params['href'] = '/BW:Potd'
local a_prev_params = { prev_lbl }
a_prev_params['href'] = prev_url
local a_next_params = { next_lbl }
a_next_params['href'] = next_url
local hd =
'<div class="bsw-card-hd">∞ Photo Lab — Picture of the Day' ..
frame:callParserFunction( '#tag:a', a_view_params ) ..
'</div>'
local nav =
'<div class="bsw-photo-nav">' ..
frame:callParserFunction( '#tag:a', a_prev_params ) ..
'<span class="bsw-photo-caption">' .. esc( caption ) .. '</span>' ..
frame:callParserFunction( '#tag:a', a_next_params ) ..
'</div>'
return '<div class="bsw-card">' .. hd .. content .. nav .. '</div>'
end
-- ── interwiki() ───────────────────────────────────────────────────
function p.interwiki( frame )
local args = frame.args
local result = '<div class="bsw-interwiki">'
for i = 1, 8 do
local flag = mw.text.trim( args[ i .. '_flag' ] or '' )
local label = mw.text.trim( args[ i .. '_label' ] or '' )
local url = mw.text.trim( args[ i .. '_url' ] or '' )
if url ~= '' then
local a_params = { label }
a_params['href'] = url
local prefix = flag ~= '' and ( flag .. ' ' ) or ''
result = result ..
'<div class="bsw-iw">' ..
prefix ..
frame:callParserFunction( '#tag:a', a_params ) ..
'</div>'
end
end
return result .. '</div>'
end
-- ── tagline() ─────────────────────────────────────────────────────
function p.tagline( frame )
-- Use frame:preprocess for wikitext bold/italic markup
local inner = frame:preprocess(
"The only original and legitimate '''Battlestar Wiki''' " ..
"— the free-as-in-beer, non-corporate, open-content encyclopedia " ..
"on all things ''Battlestar Galactica''.<br>" ..
"∞ ''Accept neither subpar substitutes nor subpar clones.'' ∞"
)
return '<div class="bsw-tagline">' .. inner .. '</div>'
end
return p