Module:SidebarPeople

From Bodhicitta
Revision as of 17:29, 21 August 2026 by Jeremi (talk | contribs) (VSCode edit)

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

local p = {}

-- Fetch display data for everyone in the sidebar in one query.
--
-- The badge template used to look each value up itself, which meant roughly
-- eight #show calls per person — a guard call and a value call for FirstImage,
-- MainNamePhon and MainNameTib each. A sidebar with six people ran about fifty
-- store lookups for what is really one question.
--
-- Two things this gets right that the old badge did not.
--
-- FirstImage is a Page-type property. Asked as ?FirstImage it renders as a
-- thumbnail and is unusable as a string, so the badge's #if guard on it always
-- saw nothing and every portrait fell back to the placeholder. The # suffix asks
-- for the raw title, which is what the filepath chain needs.
--
-- Person and organisation pages live at the TOP LEVEL, not under People/. All
-- 1701 pages in Category:People are top-level, and the properties that feed this
-- sidebar (BcaAuthor, Translator, Editor and so on) already store the bare page
-- name. The old 'People/' prefix therefore pointed at titles that do not exist.
--
-- Organisations carry Orgname rather than MainNamePhon, so both are fetched and
-- the badge falls back from one to the other.
local function fetchPeopleData(people)
    local data = {}
    if #people == 0 then
        return data
    end

    -- One disjunctive query rather than one query per person.
    local ok, result = pcall(mw.smw.ask, {
        '[[' .. table.concat(people, '||') .. ']]',
        '?FirstImage#=img',
        '?MainNamePhon=phon',
        '?MainNameTib=tib',
        '?Orgname=org',
        'mainlabel=page',
        'link=none',
        limit = 200
    })

    if not ok or type(result) ~= 'table' then
        return data
    end

    for _, row in ipairs(result) do
        local page = row.page
        if type(page) == 'table' then
            page = page.fulltext or page[1]
        end
        if page then
            data[tostring(page)] = {
                img  = row.img  or '',
                phon = row.phon or '',
                tib  = row.tib  or '',
                org  = row.org  or ''
            }
        end
    end

    return data
end

function p.main(frame)

    local pargs = frame:getParent().args
    local typesIndexed = {
        [1] = 'sourceauthor',
        [2] = 'authors',
        [3] = 'translators',
        [4] = 'contributors',
        [5] = 'editors',
        [6] = 'compilers',
        [7] = 'publisher',
        [8] = 'creators',
        [9] = 'performers',
        [10] = 'producers'
    }
    local typeLabels = {
        sourceauthor = "source author",
        authors = "author",
        translators = "translator",
        contributors = "contributor",
        editors = "editor",
        compilers = "compiler",
        publisher = "publisher",
        creators = "creator",
        performers = "performer",
        producers = "producer"
    }
    local pplTbl = {}

    for role, people in pairs(pargs) do
        for person in string.gmatch(people, "([^;]+)") do
            person = mw.text.trim(person)
            if person ~= '' then
                if pplTbl[person] then else
                    pplTbl[person] = {}
                end
                table.insert(pplTbl[person], role)
            end
        end
    end

	if next(pplTbl) == nil then
		return ''
	end

    -- One query for every person in the sidebar, instead of the badge looking
    -- each value up for itself. Sorted so the query text — and therefore the
    -- rendered output and its parser-cache entry — is stable between renders.
    local names = {}
    for person, _ in pairs(pplTbl) do
        if person ~= 'Anon' then
            table.insert(names, person)
        end
    end
    table.sort(names)
    local peopleData = fetchPeopleData(names)

    local output = '<div class="row mx-0 justify-content-center">'
    for _, ptype in ipairs(typesIndexed) do
        for person,roles in pairs(pplTbl) do
			if person == 'Anon' then 
            elseif type(roles) == 'string' then
                if ptype == roles then
                    local d = peopleData[person] or {}
                    output = output .. frame:expandTemplate{ title = 'SidebarPersonBadge', args = {
                        person ,
                        typeLabels[roles],
                        img  = d.img  or '',
                        phon = d.phon or '',
                        tib  = d.tib  or '',
                        org  = d.org  or ''
                    } }
                    pplTbl[person] = ''
                end
            elseif type(roles) == 'table' then
                for _,role in ipairs(roles) do
                    if ptype == role then
                        local d = peopleData[person] or {}
                        output = output .. frame:expandTemplate{ title = 'SidebarPersonBadge', args = {
                            person ,
                            listRoles(roles, typeLabels, typesIndexed),
                            img  = d.img  or '',
                            phon = d.phon or '',
                            tib  = d.tib  or '',
                            org  = d.org  or ''
                        } }
                        pplTbl[person] = ''
                    end
                end
            end
        end
    end
    output = output .. '</div>'

    return output

end

function sortRoles(roles, typesIndexed)
    local typeIndices = {}
    for i, role in ipairs(typesIndexed) do
        typeIndices[role] = i
    end
    
    local sortedRoles = {}
    for role, _ in pairs(roles) do
        table.insert(sortedRoles, role)
    end
    
    table.sort(sortedRoles, function(a, b)
        return typeIndices[a] < typeIndices[b]
    end)
    
    return sortedRoles
end

function listRoles(roles, typeLabels, typesIndexed)
    local uniqueRoles = {}
    for _, role in pairs(roles) do
        uniqueRoles[role] = true
    end
    
    local sortedRoles = sortRoles(uniqueRoles, typesIndexed)
    local list = {}
    
    for _, role in ipairs(sortedRoles) do
        table.insert(list, typeLabels[role])
    end
    
    return table.concat(list, ';')
end

function makeTblUnique(indexedTable)
    local uniqueTbl = {}
    for _, value in ipairs(indexedTable) do
        uniqueTbl[value] = true
    end
    indexedTable = {}
    for value,_ in pairs(uniqueTbl) do
        table.insert(indexedTable, value)
    end
    return indexedTable
end

return p