Module:Staff list
This module is used for creating a staff listing which allows all or parts of the staff to be displayed in a variety of formats. The contents are stored in a template's staff
parameter in a JSON format, including data on all individual departments and employees within the list.
Individual departments (or teams, or subteams, or other similar groups which would be marked in some way) are stored as JSON objects with the following parameters:
"name":
stores the name of the group. The value should be wrapped in translate tags so that it can be displayed in the appropriate language."id":
stores a stable identifier for the group, so that the template can request data on it."notTeam":
should be marked astrue
if the group is not a team which should be distinctly listed in lists of teams in, for example, the navbox of all departments and teams."head":
should point to the employee who is the head of the group."page":
should be the title of the page on Meta-wiki (or other Wikimedia wiki, with the appropriate interwiki prefix) which gives information about the group."members":
stores an array of all employees which are directly within the group. (Employees that are part of a subsidiary team within the group should not be listed in the members array.)"subs":
stores an array of all groups that are within the group. These groups should be formatted in the same way as the higher-level departments.
Individual employees are stored as objects with the following parameters:
"name":
stores the name of the employee."username":
stores the on-wiki username of the employee, omitting the "User:" namespace prefix."homewiki":
, for employees whose home wiki must be specified as not being on Meta-wiki, stores the interwiki prefix of the employee's home wiki. (Example:"mw"
for mediawiki.org.)"title":
stores a the name of the employee's position"titleId":
stores a stable identifier for the position, so that the template can request data on the holder of the position."isContractor":
should be markedtrue
if the employee is a contractor."isMascot":
should be markedtrue
if the "employee" is actually just a mascot which should not be counted in employee counts."image":
stores the name of an image on Commons of the employee, omitting the "File:" namespace prefix.
All of these parameters are optional except for name
. Further information on template usage can be found on Template:WMF Staff/doc.
local defaultImg = "Wikimedia Foundation office camera shy.png"
local Titlelib = require('Module:Titlelib')
function mapjoin( obj, fn, separator )
local s = ""
if ( obj ) then
for k, v in pairs( obj ) do
local r = fn( v, k )
if r and r ~= "" then
if separator and s ~= "" then
s = s .. separator
end
s = s .. r
end
end
end
return s
end
-- Decode the JSON string passed as template parameter. Escape the quotation marks
-- in Extension:Translate’s “outdated translation” and “missing translation”
-- marker before decoding to ensure the validity of the JSON string.
-- (The extension uses a <div> instead of a <span> when the translation unit
-- consists of multiple lines, but hopefully such input will never appear here,
-- as raw linebreaks in strings aren’t valid JSON in the first place.)
local function decode_translated_json( text )
text = string.gsub( text, '<span class="mw%-translate%-fuzzy">', '<span class=\\"mw-translate-fuzzy\\">' )
text = string.gsub( text, '<span lang="en" dir="ltr" class="mw%-content%-ltr">', '<span lang=\\"en\\" dir=\\"ltr\\" class=\\"mw-content-ltr\\">' )
return mw.text.jsonDecode( text )
end
-- Return a function which return true if this user hasn't already been run
-- through that function.
function startNoDup()
local cache = {}
return function ( obj )
if not obj.username then return true end
if not cache[ obj.username ] then
cache[ obj.username ] = true
return true
end
end
end
function link_user( user )
return ( user.username and
"[[" .. ( user.homewiki and user.homewiki .. ":" or "" ) .. "User:" .. user.username .. "|" .. user.name .. "]]" or
user.name
)
end
function section_link( dep )
return dep.page and "[[" .. Titlelib.myLangLink(dep.page) .. "|" .. dep.name .. "]]" or dep.name
end
function title_link( user, suppress_contractor_label )
local contractor = user.isContractor and ( not suppress_contractor_label ) and
" " .. ( mw.getCurrentFrame().args.contractor or "(Contractor)" ) or
""
return ( user.titleLink and "[[" .. user.titleLink .. "|" .. user.title .. "]]" or user.title ) .. contractor
end
-- Show a gallery entry for a single user
function gallery_entry( user )
if user then
local img = "File:" .. ( user.image ~= "" and user.image or defaultImg )
local userLink = "'''" .. link_user( user ) .. "'''"
local titleLink = title_link( user )
return img .. '|class=notpageimage|' .. userLink .. "<br /><small>" .. titleLink .. "</small>\n"
else
return ""
end
end
-- Gallery of entries for a list of users
function gallery_group( group, noDup )
return mapjoin( group, function ( v )
return ( not noDup or noDup( v ) ) and gallery_entry( v )
end )
end
-- Show a gallery group of all the users in a department/team/subteam.
function chunk_gallery( dep, size, noDup )
local s = ""
noDup = noDup or startNoDup()
if dep.head and noDup( dep.head ) then s = s .. gallery_entry( dep.head ) end
s = s .. gallery_group( dep.members, noDup )
-- s = s .. mapjoin( dep.subs, chunk_gallery )
s = s .. mapjoin( dep.subs, function ( v ) return chunk_gallery( v, size, noDup ) end )
return s
end
function gallery_tag( frame, contents, settings )
-- For testing
--return "<gallery " .. mapjoin( settings, function ( v, k ) return k .. '="' .. v .. '"' end, " " ) .. "'>" ..
-- contents .. "</gallery>"
return frame:extensionTag( "gallery", contents, settings )
end
-- Format a single department into a gallery, with headers for each team.
function department_gallery( frame, dep, size, hLevel, useSubheaders )
size = size and size ~= "" and tonumber( size ) or 200
local s = gallery_entry( dep.head ) .. gallery_group( dep.members )
local gallery_settings = { mode = "nolines",
widths = size,
-- Normal ratio is 3 / 2, but also allow 10% horizontal margin.
heights = ( math.floor( ( size * 0.9 ) / 3 * 2 ) )
}
if s ~= "" then
s = gallery_tag( frame, s, gallery_settings ) .. "\n"
end
s = s .. mapjoin( dep.subs, function ( v )
return "<h" .. hLevel .. ">" .. section_link( v ) .. "</h" .. hLevel .. ">\n" ..
(
useSubheaders and
department_gallery( frame, v, size, hLevel + 1, true )
or
gallery_tag( frame, chunk_gallery( v ), gallery_settings ) .. "\n"
)
end )
return s
end
-- Show the rows of a table containing each of the top-level department heads,
-- plus those holding certain specified roles (titleIds)
function leadership_list( staff, titleIds, withHeads )
function create_row( dep, user )
return "\n|-" ..
"\n|<div style='max-height: 150px; overflow: hidden;'>[[File:" .. ( user.image ~= "" and user.image or defaultImg ) .. "|220px|frameless|class=notpageimage]]</div>" ..
"\n|" .. link_user( user ) ..
"\n|" .. title_link( user, true ) ..
( withHeads and "\n|" .. section_link( dep ) or "" )
end
withHeads = withHeads and withHeads ~= "false"
return mapjoin( staff, function ( dep )
local r = ""
if withHeads and dep.head then
r = create_row( dep, dep.head )
end
foreach_title_id( dep, titleIds, function ( found )
r = r .. create_row( dep, found )
end )
return r
end )
end
-- Show a list of users
function group_list( group, indent )
return mapjoin( group, function( v )
return indent .. link_user( v ) .. "\n"
end )
end
-- List all members of the team/department
function section_list( dep, boldHead, indent )
local s = ""
indent = indent or "*"
if dep.head then
s = s .. indent .. ( boldHead and "'''" .. link_user( dep.head ) .. "'''" or link_user( dep.head ) ) .. "\n"
end
if dep.members then s = s .. group_list( dep.members, indent ) end
s = s .. mapjoin( dep.subs, function ( v )
return indent ..
section_link( v ) .. "\n" ..
section_list( v, false, indent .. "*" )
end )
return s
end
function create_navbox( frame, params, obj, fn )
params.bodyclass = "hlist"
-- Allow overriding navbox classes
for k, v in pairs( { "title", "above", "below", "belowclass", "name", "state", "background" } ) do
if frame.args[ v ] and frame.args[ v ] ~= "" then
params[ v ] = frame.args[ v ]
end
end
if obj then
for k, v in pairs( obj ) do
local r = fn( v, k )
params[ "group" .. k ] = r[ 1 ]
params[ "list" .. k ] = r[ 2 ]
end
end
return frame:expandTemplate{ title = "Navbox department", args = params }
end
-- Create a navbox with all teams listed and their members
function all_teams_navbox( dep, frame, extraTLRoles )
local above = ""
local noDup = startNoDup()
if dep.head and noDup( dep.head ) then
above = title_link( dep.head ) .. ": '''" .. link_user( dep.head ) .. "'''"
end
foreach_title_id( dep, extraTLRoles, function ( found )
if noDup( found ) then
above = above .. "<br />" .. title_link( found ) .. ": '''" .. link_user( found ) .. "'''"
end
end )
if dep.members then
local membersList = mapjoin( dep.members, function ( v, i )
if noDup( v ) and not v.isMascot then
return link_user( v )
end
end, " • " )
if membersList and membersList ~= "" then
above = above .. "<br />(" .. membersList .. ")"
end
end
return create_navbox( frame, {
name = "",
title = section_link( dep ),
above = above
}, dep.subs, function ( v )
return { section_link( v ), section_list( v, true ) }
end )
end
function staff_count( staff, noDup )
local staffCount, contCount = 0, 0
noDup = noDup or startNoDup()
local count_person = function ( x )
if x then
if noDup( x ) and not x.isMascot then
if x.isContractor then
contCount = contCount + 1
else
staffCount = staffCount + 1
end
end
end
end
for k, v in pairs( staff ) do
if v.head then
count_person( v.head )
end
if v.members then
for kk, vv in pairs( v.members ) do
count_person( vv )
end
end
if v.subs then
local subStaff, subCont = staff_count( v.subs, noDup )
staffCount, contCount = staffCount + subStaff, contCount + subCont
end
end
return staffCount, contCount
end
function foreach_title_id( dep, titleIds, fn )
local titleIdSet = titleIds and titleIds ~= "" and mw.text.split( titleIds, "," )
if titleIdSet then
for k, id in pairs( titleIdSet ) do
local found = find_by_title_id( dep, id )
if found then
fn( found )
end
end
end
end
-- WIP
function find_by_title_id( section, id )
if section.head and section.head.titleId == id then
return v
end
if section.members then
for k, v in pairs( section.members ) do
if v.titleId and v.titleId == id then
return v
end
end
end
if section.subs then
for k, v in pairs( section.subs ) do
local found = find_by_title_id( v, id )
if found then
return found
end
end
end
end
function find_by_id( staff, id )
for k, v in pairs( staff ) do
if v.id == id then
return v
elseif v.subs then
local found = find_by_id( v.subs, id )
if found then
return found
end
end
end
end
-- List subdivisions (not individual staffers)
function list_subs( dep, indent )
local indent = indent or "*"
return mapjoin( dep.subs, function ( team )
if not team.notTeam then
return indent .. section_link( team ) .. "\n" .. list_subs( team, indent .. "*" )
end
end )
end
-- Create "Template:Wikimedia Foundation departments" navbox
function all_departments_navbox( deps, frame )
return create_navbox( frame, {
name = ""
}, deps, function ( v, i )
-- if i == 1 then
-- Hardcode ED office row.
-- return {
-- section_link( { page = v.head.titleLink, name = ( frame.args.navbox_execbox or "Chief Executive Officer<br />and Executive Director" ) } ),
-- "* " .. link_user( v.head ) ..
-- "\n* [[Special:MyLanguage/" ..
-- ( frame.args.navbox_exec_link or "Wikimedia Foundation Leadership team" ) ..
-- "|" .. ( frame.args.navbox_exec_leadership or "Leadership" ) .. "]]"
-- }
-- else
local list = list_subs( v )
return { section_link( v ), ( list ~= "" and list or ( frame.args.noteams or "''No teams''" ) ) }
-- end
end )
end
function show_all_staff( frame, staff, arg1, arg2 )
local useSubheaders = arg1 and arg1 ~= "" and arg1 ~= "false"
local size = arg2 and arg2 ~= "" and arg2 or 200
return mapjoin( staff, function( dep )
return "<h2>" .. section_link( dep ) .. "</h2>" .. department_gallery( frame, dep, size, 3, useSubheaders ) .. "\n"
end )
end
-- Alt style based on old staff list from foundation.wikimedia wiki
-- This might be removed at some point.
function show_all_staff_alt1( frame, staff, arg1 )
return mapjoin( staff, function( dep )
local largeSize = 165
local smallSize = 140
local headblock = [[<div style="float:left;padding-top: 21px; margin-bottom:40px; max-width: 190px;">]] ..
( dep.head and gallery_tag( frame, gallery_entry( dep.head ), { mode = "nolines", widths = largeSize .. "px" } ) or "" ) ..
"</div>"
local s = gallery_group( dep.members )
if s ~= "" then
s = gallery_tag( frame, s, { mode = "nolines", widths = smallSize .. "px" } ) .. ( dep.subs and "<hr />" or "" ) .. "\n"
end
s = s .. mapjoin( dep.subs, function ( v )
return "<h3>" .. section_link( v ) .. "</h3>\n" ..
department_gallery( frame, v, smallSize, 4, true )
end, "<hr />" )
return "<h2 style='clear: left;'>" .. section_link( dep ) .. "</h2>" ..
headblock ..
[[<div style="margin-left: 200px; padding-top: 14px; color:#5E5E5E;"><p><br /></p>]] ..
s ..
"</div>" ..
"\n"
end )
end
return {
g = function( json ) -- For testing
local staff = decode_translated_json( json )
--return all_departments_navbox( staff, mw.getCurrentFrame() )
--return all_teams_navbox(
-- find_by_id( staff, 'communityengagement' ),
-- mw.getCurrentFrame(),
-- 'vpsupport'
--)
--return department_gallery( mw.getCurrentFrame(), find_by_id( staff, 'contributors' ), 200, 3 )
return show_all_staff( mw.getCurrentFrame(), staff, "", 225 )
--return show_all_staff_alt1( mw.getCurrentFrame(), staff )
--return leadership_list( staff, "vpsupport", false )
--return section_list( find_by_id( staff, 'communications' ), true )
--return staff_count( staff )
--return staff_count( { find_by_id( staff, 'contributors' ) } )
end, -- For testing
main = function ( frame )
local args = frame.args
local staff = decode_translated_json( args.staff )
mw.logObject( staff )
local type = args.type
local section = args.section
if section and section ~= "" then
section = find_by_id( staff, section )
if not section then
-- Error: Section requested, but not found.
return ""
end
else
section = staff
end
if type == "showall" then
return show_all_staff( frame, staff, args[ 1 ], args[ 2 ] )
elseif type == "department_gallery" then
-- Show gallery of single department
return department_gallery( frame, section, args[ 1 ], 3 )
elseif type == "team_gallery" then
return department_gallery( frame, section, args[ 1 ], 3 )
elseif type == "departments_navbox" then
return all_departments_navbox( staff, frame )
elseif type == "teams_navbox" then
-- Show navbox of all teams in a department, with staff
return all_teams_navbox( section, frame, args[ 1 ] )
elseif type == "team_list" then
return section_list( section, true )
elseif type == "staff_count" then
local staffCount, contCount = staff_count( section )
local r = string.gsub( args[ 1 ], "$[123]", function ( n ) return ({
["$1"] = staffCount + contCount,
["$2"] = staffCount,
["$3"] = contCount
})[ n ] or n end )
return r
elseif type == "leadership_list" then
return leadership_list( staff, args[ 1 ], args[ 2 ] )
elseif type == "showall_alt1" then
return show_all_staff_alt1( frame, staff, args[ 1 ] )
else
return error( "Staff list - invalid type" )
end
end
}