Module:SidebarPeople: Difference between revisions
From Bodhicitta
((by SublimeText.Mediawiker)) Tag: Manual revert |
(VSCode edit) |
||
| (13 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] = 'publisher', | ||
[ | [8] = 'creators', | ||
[ | [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 = {} | ||
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) | ||
if person ~= '' then | |||
if pplTbl[person] then else | |||
pplTbl[person] = {} | |||
end | |||
table.insert(pplTbl[person], role) | |||
end | end | ||
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 person == 'Anon' then | |||
elseif type(roles) == 'string' then | |||
if ptype == roles then | if ptype == roles then | ||
output = output .. frame:expandTemplate{ title = ' | 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 = ' | 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