Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

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.
Joe Beaudoin Jr. (talk | contribs)
No edit summary
Joe Beaudoin Jr. (talk | contribs)
No edit summary
Line 1: Line 1:
-- Module:BSW/MainPage
-- Module:BSW/MainPage
-- Generates main page HTML using raw string concatenation.
-- MW 1.45 compatible. Uses only sanitizer-safe HTML elements.
-- mw.html is NOT used because its output is passed through
-- <button> is NOT on the MW 1.45 allowed list — replaced with
-- MediaWiki's HTML sanitizer which strips <button> and
-- <span role="button"> which IS allowed and is wired by JS.
-- <a class="..."> elements.
-- External <a class="..."> replaced with wikitext [url text]
-- Instead we build raw HTML strings and return them via
-- processed via frame:preprocess().
-- frame:callParserFunction to inject them as strip markers
-- that survive the sanitizer pass.
--
-- Usage: {{#invoke:BSW/MainPage|functionName|param=value|...}}


local p = {}
local p = {}


-- ── HTML helpers ──────────────────────────────────────────────────
-- ── Helpers ───────────────────────────────────────────────────────


local function esc( s )
local function e( s )
     s = tostring( s or '' )
     s = tostring( s or '' )
     s = s:gsub( '&', '&amp;' )
     s = s:gsub( '&', '&amp;' ):gsub( '<', '&lt;' ):gsub( '>', '&gt;' ):gsub( '"', '&quot;' )
    s = s:gsub( '<', '&lt;' )
    s = s:gsub( '>', '&gt;' )
    s = s:gsub( '"', '&quot;' )
     return s
     return s
end
end


-- Build an attribute string, skipping empty values
local function a( name, val )
local function atr( name, value )
     if val and val ~= '' then return ' ' .. name .. '="' .. e(val) .. '"' end
     if value and value ~= '' then
        return ' ' .. name .. '="' .. esc( value ) .. '"'
    end
     return ''
     return ''
end
end


-- Wrap output so it bypasses the sanitizer.
-- Wikitext internal link: [[target|label]]
-- We use the <score> trick: any extension tag registered in MW
local function wlink( frame, target, label )
-- acts as a strip marker. But the cleanest method available in
    return frame:preprocess( '[[' .. target .. '|' .. (label or target) .. ']]' )
-- Scribunto is to mark output as safe using the internal
end
-- 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
-- External link via wikitext: [url label] — survives sanitizer
local function btn( frame, id, cls, onclick, label )
local function xlink( frame, url, label )
     local attrs = {}
     return frame:preprocess( '[' .. url .. ' ' .. (label or url) .. ']' )
    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
end


-- Build an <a> tag via #tag
-- Span acting as a button — sanitizer-safe, JS wires the behaviour
local function anchor( frame, href, cls, content, attrs_extra )
local function spanBtn( id, cls, onclick, label )
     local params = { content }
     return '<span' ..
    params['href'] = href or '#'
        a('id', id) ..
    if cls and cls ~= '' then params['class'] = cls end
        a('class', cls) ..
    if attrs_extra then
        a('role', 'button') ..
         for k, v in pairs( attrs_extra ) do
        a('tabindex', '0') ..
            params[ k ] = v
         a('onclick', onclick) ..
         end
         '>' .. (label or '') .. '</span>'
    end
    return frame:callParserFunction( '#tag:a', params )
end
end


Line 79: Line 46:
     local args  = frame.args
     local args  = frame.args
     local count = tonumber( args.count ) or 0
     local count = tonumber( args.count ) or 0
     local out  = {}
     local html = '<div class="bsw-hero">'
 
    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
     for i = 1, count do
         local cls = 'bsw-hero-dot' .. ( i == 1 and ' bsw-active' or '' )
         local pfx    = 'slide' .. i .. '_'
         local dot_params = { '' }
        local art    = mw.text.trim( args[pfx..'article']    or '' )
         dot_params['class'] = cls
         local series = mw.text.trim( args[pfx..'series']    or '' )
         dot_params['aria-label'] = 'Slide ' .. i
         local title  = mw.text.trim( args[pfx..'title']     or art )
         dots_html = dots_html .. frame:callParserFunction( '#tag:button', dot_params )
         local tlink  = mw.text.trim( args[pfx..'titlelink'] or art )
    end
         local desc  = mw.text.trim( args[pfx..'desc']      or '' )
    dots_html = dots_html .. '</div>'
        local bcolor = mw.text.trim( args[pfx..'badgecolor'] or '' )


    -- Remove the bad dots we pushed, replace
        local active  = i == 1 and ' bsw-active' or ''
    -- (simpler: just build the whole thing cleanly from scratch)
        local dot_sty  = bcolor ~= '' and ' style="background:' .. bcolor .. '"' or ''
    local result = '<div class="bsw-hero">'
        local wl      = wlink( frame, tlink, title )


    for i = 1, count do
         html = html ..
        local pfx    = 'slide' .. i .. '_'
             '<div class="bsw-slide' .. active .. '"' .. a('data-article', art) .. '>' ..
        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-bg"></div>' ..
             '<div class="bsw-slide-overlay"></div>' ..
             '<div class="bsw-slide-overlay"></div>' ..
             '<div class="bsw-slide-content">' ..
             '<div class="bsw-slide-content">' ..
             '<div class="bsw-slide-badge">' ..
             '<div class="bsw-slide-badge">' ..
             '<span class="bsw-slide-badge-dot"' .. dot_style .. '></span>' ..
             '<span class="bsw-slide-badge-dot"' .. dot_sty .. '></span>' ..
             '<span>' .. esc( series ) .. '</span>' ..
             '<span>' .. e(series) .. '</span>' ..
             '</div>' ..
             '</div>' ..
             '<div class="bsw-slide-title">' .. wlink .. '</div>' ..
             '<div class="bsw-slide-title">' .. wl .. '</div>' ..
             '<div class="bsw-slide-desc">' .. esc( desc ) .. '</div>' ..
             '<div class="bsw-slide-desc">' .. e(desc) .. '</div>' ..
             '</div>' ..
             '</div>' ..
             '</div>'
             '</div>'
     end
     end


     result = result .. dots_html
     -- Dots: span[role=button] — sanitizer safe
    html = html .. '<div class="bsw-hero-dots">'
    for i = 1, count do
        local cls = 'bsw-hero-dot' .. (i == 1 and ' bsw-active' or '')
        html = html .. spanBtn(nil, cls, 'bswGoSlide(' .. (i-1) .. ')', '')
    end
    html = html .. '</div>'


     -- Nav buttons
     -- Prev/next: span[role=button]
    local prev_params = { '‹' }
     html = html ..
    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">' ..
         '<div class="bsw-hero-nav">' ..
         frame:callParserFunction( '#tag:button', prev_params ) ..
         spanBtn('bsw-hero-prev', 'bsw-hero-btn', 'bswPrevSlide()', '&#8249;') ..
         frame:callParserFunction( '#tag:button', next_params ) ..
         spanBtn('bsw-hero-next', 'bsw-hero-btn', 'bswNextSlide()', '&#8250;') ..
         '</div>' ..
         '</div>' ..
         '</div>'
         '</div>'


     return result
     return html
end
end


Line 193: Line 100:
     local count = tonumber( args.count ) or 0
     local count = tonumber( args.count ) or 0


     local result =
     local html =
         '<div class="bsw-portals">' ..
         '<div class="bsw-portals">' ..
         '<div class="bsw-portals-label">∞ Portals of Battlestar Wiki ∞</div>' ..
         '<div class="bsw-portals-label">∞ Portals of Battlestar Wiki ∞</div>' ..
Line 200: Line 107:
     for i = 1, count do
     for i = 1, count do
         local pfx    = 'portal' .. i .. '_'
         local pfx    = 'portal' .. i .. '_'
         local href  = mw.text.trim( args[ pfx .. 'href'  ] or '#' )
         local href  = mw.text.trim( args[pfx..'href']   or '#' )
         local stripe = mw.text.trim( args[ pfx .. 'stripe' ] or '' )
         local stripe = mw.text.trim( args[pfx..'stripe'] or '' )
         local icon  = mw.text.trim( args[ pfx .. 'icon'  ] or '' )
         local icon  = mw.text.trim( args[pfx..'icon']   or '' )
         local name  = mw.text.trim( args[ pfx .. 'name'  ] or '' )
         local name  = mw.text.trim( args[pfx..'name']   or '' )
         local sub    = mw.text.trim( args[ pfx .. 'sub'    ] 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>'


        -- Internal links (/Portal:...) work as wikitext links
        -- Use span wrapper styled as link block via CSS
        local stripe_sty = stripe ~= '' and ' style="background:' .. stripe .. '"' or ''
         local inner =
         local inner =
             stripe_div ..
             '<div class="bsw-portal-stripe"' .. stripe_sty .. '></div>' ..
             '<span class="bsw-portal-icon">' .. icon .. '</span>' ..
             '<span class="bsw-portal-icon">' .. icon .. '</span>' ..
             '<span class="bsw-portal-name">' .. esc( name ) .. '</span>' ..
             '<span class="bsw-portal-name">' .. e(name) .. '</span>' ..
             '<span class="bsw-portal-sub">'  .. esc( sub  ) .. '</span>'
             '<span class="bsw-portal-sub">'  .. e(sub) .. '</span>'
 
        local a_params = { inner }
        a_params['class'] = 'bsw-portal'
        a_params['href']  = href


         result = result .. frame:callParserFunction( '#tag:a', a_params )
         -- Use MW internal link syntax — href is a /Wiki_Path
        -- Strip leading slash to get page title
        local page = href:gsub('^/', '')
        local link = frame:preprocess( '[[' .. page .. '|<div class="bsw-portal">' .. inner .. '</div>]]' )
        html = html .. link
     end
     end


     result = result .. '</div></div>'
     html = html .. '</div></div>'
     return result
     return html
end
end


-- ── statsblock() ──────────────────────────────────────────────────
-- ── card header helper ────────────────────────────────────────────
function p.statsblock( frame )
-- Returns card-hd div; link is rendered as wikitext [url text]
     local args = frame.args
local function cardHd( frame, title, linktext, linkurl )
 
     local hd = '<div class="bsw-card-hd">' .. e(title)
    local grid = '<div class="bsw-stats-grid">'
     if linkurl and linkurl ~= '' then
     for i = 1, 6 do
        -- Internal links start with /
         local n    = mw.text.trim( args[ 'n'     .. i ] or '' )
         local link
        local label = mw.text.trim( args[ 'label' .. i ] or '' )
        if linkurl:match('^/') or linkurl:match('^#') then
        if n ~= '' or label ~= '' then
            local page = linkurl:gsub('^/', '')
            -- n may contain MW magic word output already expanded
            if page == '' or page:match('^#') then
            grid = grid ..
                -- anchor-only or hash — can't wikilink, use span
                '<div class="bsw-stat">' ..
                 link = '<span class="bsw-card-hd-link">' .. e(linktext or 'More') .. '</span>'
                 '<div class="bsw-stat-n">' .. n .. '</div>' ..
            else
                '<div class="bsw-stat-l">' .. esc( label ) .. '</div>' ..
                link = frame:preprocess( '[[' .. page .. '|' .. (linktext or 'More →') .. ']]' )
                '</div>'
            end
        else
            link = xlink( frame, linkurl, linktext or 'More →' )
         end
         end
        hd = hd .. link
     end
     end
     grid = grid .. '</div>'
     hd = hd .. '</div>'
 
     return hd
    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
end


Line 287: Line 165:
     local content = args.content or ''
     local content = args.content or ''


     local hd = '<div class="bsw-card-hd">' .. esc( title )
     return '<div class="bsw-card">' ..
    if lurl ~= '' then
        cardHd( frame, title, ltext, lurl ) ..
        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 class="bsw-card-body">' .. content .. '</div>' ..
         '</div>'
         '</div>'
Line 304: Line 173:
-- ── featuredloading() ─────────────────────────────────────────────
-- ── featuredloading() ─────────────────────────────────────────────
function p.featuredloading( frame )
function p.featuredloading( frame )
     local a_params = { 'Read more ' }
     -- "Read more" link points to '#' — JS will update href
     a_params['id']  = 'bsw-featured-link'
     -- Use a span since we can't href to '#' cleanly in wikitext
    a_params['href'] = '#'
 
     local hd =
     local hd =
         '<div class="bsw-card-hd">Featured article' ..
         '<div class="bsw-card-hd">Featured article' ..
         frame:callParserFunction( '#tag:a', a_params ) ..
         '<span class="bsw-card-hd-link" id="bsw-featured-link">Read more →</span>' ..
         '</div>'
         '</div>'


     return
     return '<div class="bsw-card">' ..
        '<div class="bsw-card">' ..
         hd ..
         hd ..
         '<div class="bsw-card-body">' ..
         '<div class="bsw-card-body">' ..
Line 320: Line 186:
         '<div class="bsw-loading">' ..
         '<div class="bsw-loading">' ..
         '<div class="bsw-spinner"></div>' ..
         '<div class="bsw-spinner"></div>' ..
         'Loading today\'s featured article…' ..
         'Loading today\'s featured article\226\128\166' ..
         '</div></div></div></div>'
         '</div></div></div></div>'
end
end
Line 328: Line 194:
     local args    = frame.args
     local args    = frame.args
     local content  = args.content    or ''
     local content  = args.content    or ''
     local prev_url = mw.text.trim( args.prev_url  or '#' )
     local prev_url = mw.text.trim( args.prev_url  or '' )
     local prev_lbl = mw.text.trim( args.prev_label or '‹' )
     local prev_lbl = mw.text.trim( args.prev_label or '‹' )
     local caption  = mw.text.trim( args.caption    or '' )
     local caption  = mw.text.trim( args.caption    or '' )
     local next_url = mw.text.trim( args.next_url  or '#' )
     local next_url = mw.text.trim( args.next_url  or '' )
     local next_lbl = mw.text.trim( 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 hd =
     local hd =
         '<div class="bsw-card-hd">Photo Lab Picture of the Day' ..
         '<div class="bsw-card-hd">\226\136\158 Photo Lab \226\128\148 Picture of the Day' ..
         frame:callParserFunction( '#tag:a', a_view_params ) ..
         frame:preprocess( '[[BW:Potd|View project →]]' ) ..
         '</div>'
         '</div>'
    local prev_link = prev_url ~= '' and frame:preprocess( '[[' .. prev_url:gsub('^/','') .. '|' .. prev_lbl .. ']]' ) or prev_lbl
    local next_link = next_url ~= '' and frame:preprocess( '[[' .. next_url:gsub('^/','') .. '|' .. next_lbl .. ']]' ) or next_lbl


     local nav =
     local nav =
         '<div class="bsw-photo-nav">' ..
         '<div class="bsw-photo-nav">' ..
         frame:callParserFunction( '#tag:a', a_prev_params ) ..
         prev_link ..
         '<span class="bsw-photo-caption">' .. esc( caption ) .. '</span>' ..
         '<span class="bsw-photo-caption">' .. e(caption) .. '</span>' ..
         frame:callParserFunction( '#tag:a', a_next_params ) ..
         next_link ..
         '</div>'
         '</div>'


     return '<div class="bsw-card">' .. hd .. content .. nav .. '</div>'
     return '<div class="bsw-card">' .. hd .. content .. nav .. '</div>'
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
            grid = grid ..
                '<div class="bsw-stat">' ..
                '<div class="bsw-stat-n">' .. n .. '</div>' ..
                '<div class="bsw-stat-l">' .. e(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 page = url:gsub('^/', '')
            links = links .. frame:preprocess( '[[' .. page .. '|<span class="bsw-stat-link">' .. e(text) .. '</span>]]' )
        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 html = '<div class="bsw-wiki-tabs">'
    for i, t in ipairs(tabs) do
        local cls = 'bsw-wtab' .. (i == 1 and ' bsw-active' or '')
        html = html .. spanBtn(nil, cls, "bswSetTab(this,'" .. t[2] .. "')", t[1])
    end
    return html .. '</div>'
end
end


-- ── interwiki() ───────────────────────────────────────────────────
-- ── interwiki() ───────────────────────────────────────────────────
function p.interwiki( frame )
function p.interwiki( frame )
     local args   = frame.args
     local args = frame.args
     local result = '<div class="bsw-interwiki">'
     local html = '<div class="bsw-interwiki">'
 
     for i = 1, 8 do
     for i = 1, 8 do
         local flag  = mw.text.trim( args[ i .. '_flag' ] or '' )
         local flag  = mw.text.trim( args[i..'_flag' ] or '' )
         local label = mw.text.trim( args[ i .. '_label' ] or '' )
         local label = mw.text.trim( args[i..'_label'] or '' )
         local url  = mw.text.trim( args[ i .. '_url'   ] or '' )
         local url  = mw.text.trim( args[i..'_url' ] or '' )
         if url ~= '' then
         if url ~= '' then
             local a_params = { label }
             local link
             a_params['href'] = url
             if url:match('^/') then
             local prefix = flag ~= '' and ( flag .. ' ' ) or ''
                link = frame:preprocess( '[[' .. url:gsub('^/','') .. '|' .. label .. ']]' )
             result = result ..
            else
                '<div class="bsw-iw">' ..
                link = xlink( frame, url, label )
                prefix ..
            end
                frame:callParserFunction( '#tag:a', a_params ) ..
             local prefix = flag ~= '' and (flag .. ' ') or ''
                '</div>'
             html = html .. '<div class="bsw-iw">' .. prefix .. link .. '</div>'
         end
         end
     end
     end
 
     return html .. '</div>'
     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(
     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 " ..
         "\226\128\148 the free-as-in-beer, non-corporate, open-content encyclopedia " ..
         "on all things ''Battlestar Galactica''.<br>" ..
         "on all things ''Battlestar Galactica''.<br>" ..
         "''Accept neither subpar substitutes nor subpar clones.'' "
         "\226\136\158 ''Accept neither subpar substitutes nor subpar clones.'' \226\136\158"
     )
     )
     return '<div class="bsw-tagline">' .. inner .. '</div>'
     return '<div class="bsw-tagline">' .. inner .. '</div>'

Revision as of 16:17, 11 April 2026

Documentation for this module may be created at Module:BSW/MainPage/doc

-- Module:BSW/MainPage
-- MW 1.45 compatible. Uses only sanitizer-safe HTML elements.
-- <button> is NOT on the MW 1.45 allowed list — replaced with
-- <span role="button"> which IS allowed and is wired by JS.
-- External <a class="..."> replaced with wikitext [url text]
-- processed via frame:preprocess().

local p = {}

-- ── Helpers ───────────────────────────────────────────────────────

local function e( s )
    s = tostring( s or '' )
    s = s:gsub( '&', '&amp;' ):gsub( '<', '&lt;' ):gsub( '>', '&gt;' ):gsub( '"', '&quot;' )
    return s
end

local function a( name, val )
    if val and val ~= '' then return ' ' .. name .. '="' .. e(val) .. '"' end
    return ''
end

-- Wikitext internal link: [[target|label]]
local function wlink( frame, target, label )
    return frame:preprocess( '[[' .. target .. '|' .. (label or target) .. ']]' )
end

-- External link via wikitext: [url label] — survives sanitizer
local function xlink( frame, url, label )
    return frame:preprocess( '[' .. url .. ' ' .. (label or url) .. ']' )
end

-- Span acting as a button — sanitizer-safe, JS wires the behaviour
local function spanBtn( id, cls, onclick, label )
    return '<span' ..
        a('id', id) ..
        a('class', cls) ..
        a('role', 'button') ..
        a('tabindex', '0') ..
        a('onclick', onclick) ..
        '>' .. (label or '') .. '</span>'
end

-- ── hero() ────────────────────────────────────────────────────────
function p.hero( frame )
    local args  = frame.args
    local count = tonumber( args.count ) or 0
    local html  = '<div class="bsw-hero">'

    for i = 1, count do
        local pfx    = 'slide' .. i .. '_'
        local art    = 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 art )
        local tlink  = mw.text.trim( args[pfx..'titlelink']  or art )
        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_sty  = bcolor ~= '' and ' style="background:' .. bcolor .. '"' or ''
        local wl       = wlink( frame, tlink, title )

        html = html ..
            '<div class="bsw-slide' .. active .. '"' .. a('data-article', art) .. '>' ..
            '<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_sty .. '></span>' ..
            '<span>' .. e(series) .. '</span>' ..
            '</div>' ..
            '<div class="bsw-slide-title">' .. wl .. '</div>' ..
            '<div class="bsw-slide-desc">' .. e(desc) .. '</div>' ..
            '</div>' ..
            '</div>'
    end

    -- Dots: span[role=button] — sanitizer safe
    html = html .. '<div class="bsw-hero-dots">'
    for i = 1, count do
        local cls = 'bsw-hero-dot' .. (i == 1 and ' bsw-active' or '')
        html = html .. spanBtn(nil, cls, 'bswGoSlide(' .. (i-1) .. ')', '')
    end
    html = html .. '</div>'

    -- Prev/next: span[role=button]
    html = html ..
        '<div class="bsw-hero-nav">' ..
        spanBtn('bsw-hero-prev', 'bsw-hero-btn', 'bswPrevSlide()', '&#8249;') ..
        spanBtn('bsw-hero-next', 'bsw-hero-btn', 'bswNextSlide()', '&#8250;') ..
        '</div>' ..
        '</div>'

    return html
end

-- ── portals() ─────────────────────────────────────────────────────
function p.portals( frame )
    local args  = frame.args
    local count = tonumber( args.count ) or 0

    local html =
        '<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 '' )

        -- Internal links (/Portal:...) work as wikitext links
        -- Use span wrapper styled as link block via CSS
        local stripe_sty = stripe ~= '' and ' style="background:' .. stripe .. '"' or ''
        local inner =
            '<div class="bsw-portal-stripe"' .. stripe_sty .. '></div>' ..
            '<span class="bsw-portal-icon">' .. icon .. '</span>' ..
            '<span class="bsw-portal-name">' .. e(name) .. '</span>' ..
            '<span class="bsw-portal-sub">'  .. e(sub)  .. '</span>'

        -- Use MW internal link syntax — href is a /Wiki_Path
        -- Strip leading slash to get page title
        local page = href:gsub('^/', '')
        local link = frame:preprocess( '[[' .. page .. '|<div class="bsw-portal">' .. inner .. '</div>]]' )
        html = html .. link
    end

    html = html .. '</div></div>'
    return html
end

-- ── card header helper ────────────────────────────────────────────
-- Returns card-hd div; link is rendered as wikitext [url text]
local function cardHd( frame, title, linktext, linkurl )
    local hd = '<div class="bsw-card-hd">' .. e(title)
    if linkurl and linkurl ~= '' then
        -- Internal links start with /
        local link
        if linkurl:match('^/') or linkurl:match('^#') then
            local page = linkurl:gsub('^/', '')
            if page == '' or page:match('^#') then
                -- anchor-only or hash — can't wikilink, use span
                link = '<span class="bsw-card-hd-link">' .. e(linktext or 'More') .. '</span>'
            else
                link = frame:preprocess( '[[' .. page .. '|' .. (linktext or 'More →') .. ']]' )
            end
        else
            link = xlink( frame, linkurl, linktext or 'More →' )
        end
        hd = hd .. link
    end
    hd = hd .. '</div>'
    return hd
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 ''

    return '<div class="bsw-card">' ..
        cardHd( frame, title, ltext, lurl ) ..
        '<div class="bsw-card-body">' .. content .. '</div>' ..
        '</div>'
end

-- ── featuredloading() ─────────────────────────────────────────────
function p.featuredloading( frame )
    -- "Read more" link points to '#' — JS will update href
    -- Use a span since we can't href to '#' cleanly in wikitext
    local hd =
        '<div class="bsw-card-hd">Featured article' ..
        '<span class="bsw-card-hd-link" id="bsw-featured-link">Read more →</span>' ..
        '</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\226\128\166' ..
        '</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 hd =
        '<div class="bsw-card-hd">\226\136\158 Photo Lab \226\128\148 Picture of the Day' ..
        frame:preprocess( '[[BW:Potd|View project →]]' ) ..
        '</div>'

    local prev_link = prev_url ~= '' and frame:preprocess( '[[' .. prev_url:gsub('^/','') .. '|' .. prev_lbl .. ']]' ) or prev_lbl
    local next_link = next_url ~= '' and frame:preprocess( '[[' .. next_url:gsub('^/','') .. '|' .. next_lbl .. ']]' ) or next_lbl

    local nav =
        '<div class="bsw-photo-nav">' ..
        prev_link ..
        '<span class="bsw-photo-caption">' .. e(caption) .. '</span>' ..
        next_link ..
        '</div>'

    return '<div class="bsw-card">' .. hd .. content .. nav .. '</div>'
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
            grid = grid ..
                '<div class="bsw-stat">' ..
                '<div class="bsw-stat-n">' .. n .. '</div>' ..
                '<div class="bsw-stat-l">' .. e(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 page = url:gsub('^/', '')
            links = links .. frame:preprocess( '[[' .. page .. '|<span class="bsw-stat-link">' .. e(text) .. '</span>]]' )
        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 html = '<div class="bsw-wiki-tabs">'
    for i, t in ipairs(tabs) do
        local cls = 'bsw-wtab' .. (i == 1 and ' bsw-active' or '')
        html = html .. spanBtn(nil, cls, "bswSetTab(this,'" .. t[2] .. "')", t[1])
    end
    return html .. '</div>'
end

-- ── interwiki() ───────────────────────────────────────────────────
function p.interwiki( frame )
    local args = frame.args
    local html = '<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 link
            if url:match('^/') then
                link = frame:preprocess( '[[' .. url:gsub('^/','') .. '|' .. label .. ']]' )
            else
                link = xlink( frame, url, label )
            end
            local prefix = flag ~= '' and (flag .. ' ') or ''
            html = html .. '<div class="bsw-iw">' .. prefix .. link .. '</div>'
        end
    end
    return html .. '</div>'
end

-- ── tagline() ─────────────────────────────────────────────────────
function p.tagline( frame )
    local inner = frame:preprocess(
        "The only original and legitimate '''Battlestar Wiki''' " ..
        "\226\128\148 the free-as-in-beer, non-corporate, open-content encyclopedia " ..
        "on all things ''Battlestar Galactica''.<br>" ..
        "\226\136\158 ''Accept neither subpar substitutes nor subpar clones.'' \226\136\158"
    )
    return '<div class="bsw-tagline">' .. inner .. '</div>'
end

return p