Module:ElementPortraitCarousel
Appearance
Documentation for this module may be created at Module:ElementPortraitCarousel/doc
--[[
* Module:ElementPortraitCarousel
* Renders a portrait carousel for person templates with 3 display modes:
* 1. Single image - Simple centered display
* 2. Dual images - Orbital display with animation
* 3. Multi-image - Full carousel with navigation arrows
*
* Integrates with Blueprint template system.
]]
local p = {}
p.elementName = "portraitCarousel"
-- Load required modules
local ErrorHandling = require('Module:ErrorHandling')
-- Default configuration
p.defaultConfig = {
maxWidth = "220px",
carouselClass = "person-portrait-carousel"
}
-- Helper function to create a properly formatted image URL
local function formatImageUrl(imageName)
-- Remove any File: or Image: prefix if present
imageName = imageName:gsub('^[Ff]ile:', '')
imageName = imageName:gsub('^[Ii]mage:', '')
-- Format the URL using MediaWiki's makeImageLink function
local imageTitle = mw.title.new('File:' .. imageName)
if not imageTitle then
return '' -- Invalid title
end
-- Use thumb with a large size to get the full image
local imageObj = mw.image.new(imageTitle.text)
if not imageObj then
return '' -- Image doesn't exist
end
return imageObj:url()
end
-- Split portrait field by semicolons
local function splitPortraitImages(portrait)
if not portrait or portrait == "" then
return {}
end
local images = {}
for image in portrait:gmatch("[^;]+") do
local imageName = mw.text.trim(image)
local imageUrl = formatImageUrl(imageName)
if imageUrl and imageUrl ~= "" then
table.insert(images, {
name = imageName,
url = imageUrl
})
end
end
return images
end
-- Create single image HTML
local function createSingleImage(imageData, maxWidth)
return string.format(
'<div class="person-portrait-carousel">' ..
'<img src="%s" style="max-width:%s;" alt="Portrait" />' ..
'</div>',
imageData.url, maxWidth
)
end
-- Create dual-image orbital display HTML
local function createDualImages(images, maxWidth)
local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person")
return string.format(
'<div class="person-portrait-carousel">' ..
'<div class="carousel-container">' ..
'<div class="carousel-images" id="carousel-%s">' ..
'<div class="carousel-item carousel-orbital-1" data-index="1"><img src="%s" alt="Portrait 1" /></div>' ..
'<div class="carousel-item carousel-orbital-2" data-index="2"><img src="%s" alt="Portrait 2" /></div>' ..
'</div></div></div>',
navId,
images[1].url, images[2].url
)
end
-- Create multi-image carousel HTML
local function createCarousel(images, maxWidth)
local itemsHtml = {}
local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person")
-- Generate carousel items
for i, imageData in ipairs(images) do
local class = i == 1 and "carousel-visible" or "carousel-hidden"
-- Add positioning classes for 3+ images
if i == 2 then
class = class .. " carousel-right"
elseif i == #images then
class = class .. " carousel-left"
end
table.insert(itemsHtml, string.format(
'<div class="carousel-item %s" data-index="%d"><img src="%s" alt="Portrait %d" /></div>',
class, i, imageData.url, i
))
end
-- Build complete carousel HTML
return string.format(
'<div class="person-portrait-carousel">' ..
'<div class="carousel-container">' ..
'<div class="carousel-nav carousel-prev">◀</div>' ..
'<div class="carousel-images" id="carousel-%s">%s</div>' ..
'<div class="carousel-nav carousel-next">▶</div>' ..
'</div></div>',
navId, table.concat(itemsHtml)
)
end
-- Create block function
function p.createBlock()
return function(template, args)
-- Protected execution wrapper
local function execute()
local portrait = args.portrait or ""
local images = splitPortraitImages(portrait)
local maxWidth = template.config.portraitCarousel
and template.config.portraitCarousel.maxWidth
or p.defaultConfig.maxWidth
-- Determine display type based on number of images
if #images == 0 then
return "" -- No images
elseif #images == 1 then
return createSingleImage(images[1], maxWidth)
elseif #images == 2 then
return createDualImages(images, maxWidth)
else
return createCarousel(images, maxWidth)
end
end
-- Wrap with error handling
if template._errorContext then
return ErrorHandling.protect(
template._errorContext,
"ElementBlock_portraitCarousel",
execute,
""
)
else
local ok, result = pcall(execute)
return ok and result or ""
end
end
end
return p