Module:SidebarPeople: Difference between revisions

From Bodhicitta
((by SublimeText.Mediawiker))
(VSCode edit)
 
(15 intermediate revisions by the same user not shown)
Line 1: Line 1:
local p = {}
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.
--
-- Most people live under People/ (2172 pages), with top-level redirects pointing
-- into them. But not all — some people and most organisations sit at the top
-- level with no People/ counterpart at all, for example 'Almogi, O.' and
-- 'Department of Indian and Tibetan Studies, Universität Hamburg'. Assuming
-- either location alone leaves those badges blank, which is what the hardcoded
-- People/ prefix used to do.
--
-- So both titles are asked for in the same query and whichever exists wins.
--
-- 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 covering both possible locations for every person.
    local titles = {}
    for _, person in ipairs(people) do
        table.insert(titles, 'People/' .. person)
        table.insert(titles, person)
    end
    local ok, result = pcall(mw.smw.ask, {
        '[[' .. table.concat(titles, '||') .. ']]',
        -- ?#-=page returns the RAW page title. mainlabel=page instead returns a
        -- rendered wikilink like [[:People/X|X]], which cannot be matched back
        -- to the name the badge was called with — that mismatch left every
        -- portrait blank even once the query itself was correct.
        '?#-=page',
        '?FirstImage#=img',
        '?MainNamePhon=phon',
        '?MainNameTib=tib',
        '?Orgname=org',
        'mainlabel=-',
        '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
            -- Key on the bare name so the badge can look it up by the value it
            -- was called with, whichever location the page turned out to be in.
            local bare = tostring(page):gsub('^People/', '')
            local entry = {
                img  = row.img  or '',
                phon = row.phon or '',
                tib  = row.tib  or '',
                org  = row.org  or ''
            }
            -- A People/ page wins over a top-level one of the same name, since
            -- the top-level title is usually just a redirect carrying no data.
            local existing = data[bare]
            if existing == nil or (existing.img == '' and entry.img ~= '') then
                data[bare] = entry
            end
        end
    end
    return data
end


function p.main(frame)
function p.main(frame)
Line 10: Line 95:
         [4] = 'contributors',
         [4] = 'contributors',
         [5] = 'editors',
         [5] = 'editors',
         [6] = 'publisher',
         [6] = 'compilers',
         [7] = 'creators',
        [7] = 'publisher',
         [8] = 'performers',
         [8] = 'creators',
         [9] = 'producers'
         [9] = 'performers',
         [10] = 'producers'
     }
     }
     local typeLabels = {
     local typeLabels = {
Line 21: Line 107:
         contributors = "contributor",
         contributors = "contributor",
         editors = "editor",
         editors = "editor",
        compilers = "compiler",
         publisher = "publisher",
         publisher = "publisher",
         creators = "creator",
         creators = "creator",
Line 28: Line 115:
     local pplTbl = {}
     local pplTbl = {}


    local output = '<div class="d-flex justify-content-center flex-row flex-wrap">'
     for role, people in pairs(pargs) do
     for role, people in pairs(pargs) do
         for person in string.gmatch(people, "([^;]+)") do
         for person in string.gmatch(people, "([^;]+)") do
             if pplTbl[person] then else
             person = mw.text.trim(person)
                pplTbl[person] = {}
            if person ~= '' then
                if pplTbl[person] then else
                    pplTbl[person] = {}
                end
                table.insert(pplTbl[person], role)
             end
             end
            table.insert(pplTbl[person], role)
         end
         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)
    -- Not a Bootstrap .row any more: the badges are compact full-width rows, and
    -- the grid's negative x-margins have no container here to absorb them.
    --
    -- tws-capped collapses the tail past data-cap behind a "+N more" toggle, and
    -- caps direct children, which the badges are. Most items have one or two
    -- people, so the cap only ever fires on the long tail — one book here has 26.
    -- Same .sidebar-section wrapper and heading as the links and rKTs blocks
    -- below, so the three read as one sequence rather than as a bare list
    -- followed by two labelled ones.
    --
    -- Not a Bootstrap .row: the badges are compact full-width rows, and the
    -- grid's negative x-margins have no container here to absorb them.
    --
    -- tws-capped collapses the tail past data-cap behind a "+N more" toggle, and
    -- caps direct children, which the badges are. Most items have one or two
    -- people, so the cap only ever fires on the long tail — one book here has 26.
    local output = '<div class="sidebar-section">'
        .. '<div class="sidebar-section-heading">People</div>'
        .. '<div class="sidebar-people tws-capped" data-cap="8"'
        .. ' data-items=".sidebar-person" data-noun="people">'
     for _, ptype in ipairs(typesIndexed) do
     for _, ptype in ipairs(typesIndexed) do
         for person,roles in pairs(pplTbl) do
         for person,roles in pairs(pplTbl) do
             if type(roles) == 'string' then
if person == 'Anon' then
             elseif type(roles) == 'string' then
                 if ptype == roles then
                 if ptype == roles then
                     output = output .. frame:expandTemplate{ title = 'PersonBadge', args = {  
                    local d = peopleData[person] or {}
                         person ,  
                     output = output .. frame:expandTemplate{ title = 'SidebarPersonBadge', args = {
                         typeLabels[roles]
                         person ,
                         typeLabels[roles],
                        img  = d.img  or '',
                        phon = d.phon or '',
                        tib  = d.tib  or '',
                        org  = d.org  or ''
                     } }
                     } }
                     pplTbl[person] = ''
                     pplTbl[person] = ''
Line 50: Line 182:
                 for _,role in ipairs(roles) do
                 for _,role in ipairs(roles) do
                     if ptype == role then
                     if ptype == role then
                         output = output .. frame:expandTemplate{ title = 'PersonBadge', args = {  
                        local d = peopleData[person] or {}
                             person ,  
                         output = output .. frame:expandTemplate{ title = 'SidebarPersonBadge', args = {
                             listRoles(roles, typeLabels, typesIndexed)
                             person ,
                             listRoles(roles, typeLabels, typesIndexed),
                            img  = d.img  or '',
                            phon = d.phon or '',
                            tib  = d.tib  or '',
                            org  = d.org  or ''
                         } }
                         } }
                         pplTbl[person] = ''
                         pplTbl[person] = ''
Line 60: Line 197:
         end
         end
     end
     end
     output = output .. '</div>'
     output = output .. '</div></div>'


     return output
     return output

Latest revision as of 14:08, 25 August 2026

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.
--
-- Most people live under People/ (2172 pages), with top-level redirects pointing
-- into them. But not all — some people and most organisations sit at the top
-- level with no People/ counterpart at all, for example 'Almogi, O.' and
-- 'Department of Indian and Tibetan Studies, Universität Hamburg'. Assuming
-- either location alone leaves those badges blank, which is what the hardcoded
-- People/ prefix used to do.
--
-- So both titles are asked for in the same query and whichever exists wins.
--
-- 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 covering both possible locations for every person.
    local titles = {}
    for _, person in ipairs(people) do
        table.insert(titles, 'People/' .. person)
        table.insert(titles, person)
    end

    local ok, result = pcall(mw.smw.ask, {
        '[[' .. table.concat(titles, '||') .. ']]',
        -- ?#-=page returns the RAW page title. mainlabel=page instead returns a
        -- rendered wikilink like [[:People/X|X]], which cannot be matched back
        -- to the name the badge was called with — that mismatch left every
        -- portrait blank even once the query itself was correct.
        '?#-=page',
        '?FirstImage#=img',
        '?MainNamePhon=phon',
        '?MainNameTib=tib',
        '?Orgname=org',
        'mainlabel=-',
        '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
            -- Key on the bare name so the badge can look it up by the value it
            -- was called with, whichever location the page turned out to be in.
            local bare = tostring(page):gsub('^People/', '')
            local entry = {
                img  = row.img  or '',
                phon = row.phon or '',
                tib  = row.tib  or '',
                org  = row.org  or ''
            }
            -- A People/ page wins over a top-level one of the same name, since
            -- the top-level title is usually just a redirect carrying no data.
            local existing = data[bare]
            if existing == nil or (existing.img == '' and entry.img ~= '') then
                data[bare] = entry
            end
        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)

    -- Not a Bootstrap .row any more: the badges are compact full-width rows, and
    -- the grid's negative x-margins have no container here to absorb them.
    --
    -- tws-capped collapses the tail past data-cap behind a "+N more" toggle, and
    -- caps direct children, which the badges are. Most items have one or two
    -- people, so the cap only ever fires on the long tail — one book here has 26.
    -- Same .sidebar-section wrapper and heading as the links and rKTs blocks
    -- below, so the three read as one sequence rather than as a bare list
    -- followed by two labelled ones.
    --
    -- Not a Bootstrap .row: the badges are compact full-width rows, and the
    -- grid's negative x-margins have no container here to absorb them.
    --
    -- tws-capped collapses the tail past data-cap behind a "+N more" toggle, and
    -- caps direct children, which the badges are. Most items have one or two
    -- people, so the cap only ever fires on the long tail — one book here has 26.
    local output = '<div class="sidebar-section">'
        .. '<div class="sidebar-section-heading">People</div>'
        .. '<div class="sidebar-people tws-capped" data-cap="8"'
        .. ' data-items=".sidebar-person" data-noun="people">'
    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></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