Soft launch: Soldier biographies and research notes vary in completeness. Prefer class-roster, NARA, newspapers, and other cited sources for contested facts — treat Research notes as leads, not settled history. Anyone can correct a page; contact us with questions.
Module:Featured
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Featured/doc
-- Weekly rotating featured soldiers for the Main Page (level-2 curated pool).
-- Pool page: [[Featured pool]] — one bullet per soldier; sysop-edit only.
--
-- Line formats (pipe-separated, optional fields):
-- * Page title
-- * Page title | Caption
-- * Page title | Caption | Filename.jpg
-- If caption or photo is omitted, the module derives them from the soldier page.
local p = {}
local POOL_TITLE = 'Featured pool'
local DEFAULT_COUNT = 3
local function trim(s)
return mw.text.trim(tostring(s or ''))
end
local function displayFromTitle(title)
title = trim(title)
-- "Surname, Given (ASN)" → "Given Surname"
local surname, given = mw.ustring.match(title, '^([^,]+),%s*(.-)%s*%(%d')
if surname and given then
given = mw.ustring.gsub(given, '%s+$', '')
return trim(given .. ' ' .. surname)
end
surname, given = mw.ustring.match(title, '^([^,]+),%s*(.+)$')
if surname and given then
return trim(given .. ' ' .. surname)
end
return title
end
local function extractPhoto(wikitext)
if not wikitext or wikitext == '' then
return ''
end
local photo = mw.ustring.match(wikitext, '|%s*photo%s*=%s*([^\n|}]+)')
if not photo then
return ''
end
photo = trim(photo)
photo = mw.ustring.gsub(photo, '^%[%[File:', '')
photo = mw.ustring.gsub(photo, '%]%]$', '')
photo = mw.ustring.gsub(photo, '|.*$', '')
photo = mw.ustring.gsub(photo, '^File:', '')
return trim(photo)
end
local function parseLine(line)
line = trim(line)
if line == '' then
return nil
end
-- Strip list markers; skip headings and HTML comments
line = mw.ustring.gsub(line, '^[*#:;]+%s*', '')
if line == '' or mw.ustring.sub(line, 1, 1) == '=' then
return nil
end
if mw.ustring.find(line, '^%<%!%-%-', 1, true) then
return nil
end
-- [[Title|Label]] or [[Title]] at the start → plain fields
line = mw.ustring.gsub(line, '^%[%[([^%]|]+)|([^%]]+)%]%]', '%1|%2')
line = mw.ustring.gsub(line, '^%[%[([^%]|]+)%]%]', '%1')
local parts = mw.text.split(line, '|')
local title = trim(parts[1] or '')
local caption = trim(parts[2] or '')
local photo = trim(parts[3] or '')
if title == '' then
return nil
end
-- Skip leftovers from comments / markup
if mw.ustring.find(title, '^[%s%p]*$') or mw.ustring.find(title, '^%<') then
return nil
end
if not mw.ustring.find(title, '%a') then
return nil
end
if photo ~= '' then
photo = mw.ustring.gsub(photo, '^File:', '')
end
return {
title = title,
caption = caption,
photo = photo,
}
end
local function loadPool()
local titleObj = mw.title.new(POOL_TITLE)
if not titleObj or not titleObj.exists then
return {}
end
local content = titleObj:getContent()
if not content or content == '' then
return {}
end
-- Only read bullets under == Pool == (ignore examples elsewhere on the page)
local section = mw.ustring.match(content, '\n==%s*Pool%s*==\n(.-)\n==%s*')
if not section then
section = mw.ustring.match(content, '^==%s*Pool%s*==\n(.-)\n==%s*')
end
if not section then
section = mw.ustring.match(content, '\n==%s*Pool%s*==\n(.*)$')
or mw.ustring.match(content, '^==%s*Pool%s*==\n(.*)$')
end
if not section then
return {}
end
local entries = {}
for line in mw.text.gsplit(section, '\n', true) do
local entry = parseLine(line)
if entry then
table.insert(entries, entry)
end
end
return entries
end
local function enrich(entry)
local title = entry.title
local caption = entry.caption
local photo = entry.photo
if caption == '' then
caption = displayFromTitle(title)
end
if photo == '' then
local page = mw.title.new(title)
if page and page.exists then
photo = extractPhoto(page:getContent() or '')
end
end
return {
title = title,
caption = caption,
photo = photo,
}
end
local function weekOffset(n)
if n < 1 then
return 0
end
local lang = mw.getContentLanguage()
-- ISO week-of-year + ISO week-year so Dec/Jan boundary stays stable
local week = tonumber(lang:formatDate('W')) or 1
local year = tonumber(lang:formatDate('o')) or tonumber(lang:formatDate('Y')) or 2026
local key = year * 53 + week
return key % n
end
local function renderThumb(entry)
local title = entry.title
local caption = entry.caption
local photo = entry.photo
local link = string.format('[[%s|%s]]', title, caption)
if photo ~= '' then
return string.format(
'<div class="home-feature-thumb">[[File:%s|150x220px|link=%s|alt=%s]]'
.. '<div class="home-feature-caption">%s</div></div>',
photo,
title,
caption,
link
)
end
-- No photo yet: caption-only card so the pool entry still appears
return string.format(
'<div class="home-feature-thumb home-feature-thumb-nophoto">'
.. '<div class="home-feature-caption">%s</div></div>',
link
)
end
function p.main(frame)
local args = frame.args
local parent = frame:getParent()
if parent and parent.args then
for k, v in pairs(parent.args) do
if args[k] == nil or args[k] == '' then
args[k] = v
end
end
end
local count = tonumber(args.count or args[1]) or DEFAULT_COUNT
if count < 1 then
count = DEFAULT_COUNT
end
if count > 12 then
count = 12
end
local pool = loadPool()
if #pool == 0 then
return '<div class="home-feature-thumbs"><!-- Featured pool is empty — edit [[Featured pool]] --></div>'
end
local offset = weekOffset(#pool)
local parts = { '<div class="home-feature-thumbs">' }
for i = 0, count - 1 do
local idx = ((offset + i) % #pool) + 1
table.insert(parts, renderThumb(enrich(pool[idx])))
end
table.insert(parts, '</div>')
return table.concat(parts)
end
-- Debug helper: {{#invoke:Featured|preview|offset=0|count=3}}
function p.preview(frame)
local args = frame.args
local count = tonumber(args.count) or DEFAULT_COUNT
local pool = loadPool()
if #pool == 0 then
return 'Pool empty'
end
local offset = tonumber(args.offset) or weekOffset(#pool)
offset = offset % #pool
local lines = {
string.format('Pool size: %d; offset: %d; showing %d', #pool, offset, count),
}
for i = 0, count - 1 do
local idx = ((offset + i) % #pool) + 1
local e = enrich(pool[idx])
table.insert(
lines,
string.format('* %s — %s — photo=%s', e.title, e.caption, e.photo ~= '' and e.photo or '(none)')
)
end
return table.concat(lines, '\n')
end
return p