Module:UserProfile
Appearance
Documentation for this module may be created at Module:UserProfile/doc
-- Module:UserProfile
-- Usage on User:WikiSysop: {{#invoke:UserProfile|founder}}
-- Data lives in Module:UserProfile/config.
local p = {}
local cfg = mw.loadData('Module:UserProfile/config')
local lang = mw.language.getContentLanguage()
-- ---------- helpers ----------
-- mw.loadData tables are read-only proxies: use ipairs only, never next(),
-- # or table.concat on them. Everything below copies into local tables first.
local function each(t)
return ipairs(t or {})
end
local function isEmpty(t)
return t == nil or t[1] == nil
end
-- True if a value (or anything nested in it) still holds an unfilled « » placeholder
local function hasPlaceholder(v)
if type(v) == 'string' then
return v:find('«', 1, true) ~= nil
elseif type(v) == 'table' then
for _, x in ipairs(v) do
if hasPlaceholder(x) then return true end
end
end
return false
end
-- Returns a plain local array without placeholder entries
local function clean(items)
local out = {}
for _, it in each(items) do
if not hasPlaceholder(it) then out[#out + 1] = it end
end
return out
end
local function section(title, body)
if body == nil or body == '' then return nil end
return '== ' .. title .. ' ==\n' .. body
end
local function subsection(title, body)
if body == nil or body == '' then return nil end
return '=== ' .. title .. ' ===\n' .. body
end
local function list(items, ordered)
items = clean(items)
if isEmpty(items) then return '' end
local mark = ordered and '# ' or '* '
local out = {}
for _, item in ipairs(items) do
out[#out + 1] = mark .. tostring(item)
end
return table.concat(out, '\n')
end
-- Two-column table, first column as row headers
local function kvTable(rows, caption, collapsed)
rows = clean(rows)
if isEmpty(rows) then return '' end
local t = mw.html.create('table'):addClass('wikitable')
if collapsed then
t:addClass('mw-collapsible'):addClass('mw-collapsed')
end
if caption then
t:tag('tr'):tag('th'):attr('colspan', 2):wikitext(caption)
end
for _, r in ipairs(rows) do
local tr = t:tag('tr')
tr:tag('th'):attr('scope', 'row'):wikitext(tostring(r[1] or ''))
tr:tag('td'):wikitext(tostring(r[2] or ''))
end
return tostring(t)
end
-- Table with a header row
local function gridTable(headers, rows)
rows = clean(rows)
if isEmpty(rows) then return '' end
local t = mw.html.create('table'):addClass('wikitable')
local head = t:tag('tr')
for _, h in each(headers) do
head:tag('th'):attr('scope', 'col'):wikitext(h)
end
for _, r in ipairs(rows) do
local tr = t:tag('tr')
for _, c in each(r) do
tr:tag('td'):wikitext(tostring(c))
end
end
return tostring(t)
end
local function link(title, label)
return string.format('[[%s|%s]]', title, label)
end
-- Format a MediaWiki timestamp safely; returns nil on failure
local function safeDate(fmt, ts)
local ok, res = pcall(function() return lang:formatDate(fmt, ts) end)
if ok and res and res ~= '' then return res end
return nil
end
local function revisionDate(frame, fmt)
frame = frame or mw.getCurrentFrame()
local ts = frame:preprocess('{{REVISIONTIMESTAMP}}')
if ts and ts ~= '' then
return safeDate(fmt, ts)
end
return nil
end
-- ---------- blocks ----------
local function infobox(frame)
local t = mw.html.create('table'):addClass('wikitable'):addClass('floatright')
t:attr('style', 'width:22em;')
t:tag('tr'):tag('th'):attr('colspan', 2):wikitext(cfg.owner or mw.title.getCurrentTitle().text)
for _, r in ipairs(clean(cfg.infobox)) do
local tr = t:tag('tr')
tr:tag('th'):attr('scope', 'row'):wikitext(tostring(r[1] or ''))
tr:tag('td'):wikitext(tostring(r[2] or ''))
end
local d = revisionDate(frame, 'j F Y')
if d then
local tr = t:tag('tr')
tr:tag('th'):attr('scope', 'row'):wikitext('Last updated')
tr:tag('td'):wikitext(d)
end
return tostring(t)
end
local function stats()
local s = mw.site.stats
local data = {
{ 'Articles', s.articles },
{ 'All pages', s.pages },
{ 'Files', s.files },
{ 'Registered users', s.users },
{ 'Active users', s.activeUsers },
{ 'Administrators', s.admins },
{ 'Total edits', s.edits },
}
local rows = {}
for _, d in ipairs(data) do
rows[#rows + 1] = { d[1], lang:formatNum(d[2] or 0) }
end
return gridTable({ 'Metric', 'Value' }, rows)
end
local function contentStatus()
local rows = {}
for _, cat in each(cfg.watchCategories) do
local n = mw.site.stats.pagesInCategory(cat, 'pages') or 0
rows[#rows + 1] = {
string.format('[[:Category:%s|%s]]', cat, cat),
lang:formatNum(n),
}
end
return gridTable({ 'Category', 'Pages' }, rows)
end
-- Always links the page, so a missing one shows as a red link you can click to create
local function policies()
local rows = {}
for _, pol in each(cfg.policies) do
local title = mw.title.new(pol[1])
local exists = title and title.exists
rows[#rows + 1] = {
link(pol[1], pol[2]),
exists and 'Exists' or "''Missing''",
}
end
return gridTable({ 'Policy page', 'Status' }, rows)
end
local function dashboard()
local rows = {}
for _, group in each(cfg.tools) do
local links = {}
for _, l in ipairs(clean(group[2])) do
links[#links + 1] = link(l[1], l[2])
end
if #links > 0 then
rows[#rows + 1] = { group[1], table.concat(links, ' · ') }
end
end
return kvTable(rows, 'Maintenance and moderation tools', true)
end
local function faq()
return kvTable(cfg.faq, 'Questions about the wiki and my role', true)
end
local function categories()
local out = {}
for _, c in each(cfg.categories) do
out[#out + 1] = '[[Category:' .. c .. ']]'
end
return table.concat(out, '\n')
end
-- ---------- main entry ----------
function p.founder(frame)
frame = frame or mw.getCurrentFrame()
local out = {}
local function add(s)
if s and s ~= '' then out[#out + 1] = s end
end
add(infobox(frame))
add(cfg.lead)
add('<br clear="all" />')
add(section("Founder's statement", cfg.statement))
add(section('Mission and vision', kvTable(cfg.mission)))
local bg = {}
local function addBg(s) if s then bg[#bg + 1] = s end end
if cfg.story and not hasPlaceholder(cfg.story) then
addBg(subsection('Founding story', cfg.story))
end
addBg(subsection('Expertise', list(cfg.expertise)))
addBg(subsection('Timeline', gridTable({ 'Date', 'Milestone' }, cfg.timeline)))
add(section('Background', table.concat(bg, '\n\n')))
add(section('Responsibilities', gridTable({ 'Area', 'What I do' }, cfg.responsibilities)))
add(section('Editorial principles', list(cfg.principles, true)))
add(section('Governance', list(cfg.governance)))
add(section('Technical stewardship', kvTable(cfg.tech)))
add(section('Get involved', list(cfg.involved, true)))
add(section('Wiki at a glance', stats()))
add(section('Content status', contentStatus()))
add(section('Admin dashboard', dashboard()))
add(section('Policy pages', policies()))
add(section('Frequently asked questions', faq()))
add(section('Contact', list(cfg.contact)))
local d = revisionDate(frame, 'F Y')
add("----\n''Founder profile of the wiki." .. (d and (' Last updated ' .. d .. '.') or '') .. "''")
add(categories())
return table.concat(out, '\n\n')
end
-- Reusable sections for other pages: {{#invoke:UserProfile|stats}} etc.
function p.stats() return stats() end
function p.contentStatus() return contentStatus() end
function p.dashboard() return dashboard() end
function p.policies() return policies() end
p.main = p.founder
return p