Module:ElementPortraitCarousel: Difference between revisions
Appearance
// via Wikitext Extension for VSCode |
// via Wikitext Extension for VSCode |
||
| (6 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
--[[ | --[[ | ||
* Name: ElementPortraitCarousel | |||
* Author: Mark W. Datysgeld | |||
* Description: Element module that renders portrait carousels for Person templates with three display modes based on image count | |||
* Notes: Single image (centered display); dual images (orbital display with animation); multi-image (full carousel with navigation arrows); integrates with Blueprint template system; image sanitization; protected execution with error handling | |||
]] | |||
local p = {} | local p = {} | ||
| Line 15: | Line 12: | ||
-- Load required modules | -- Load required modules | ||
local ErrorHandling = require('Module:ErrorHandling') | local ErrorHandling = require('Module:ErrorHandling') | ||
local TemplateHelpers = require('Module:TemplateHelpers') | |||
-- Default configuration | -- Default configuration | ||
| Line 30: | Line 28: | ||
local images = {} | local images = {} | ||
for image in portrait:gmatch("[^;]+") do | for image in portrait:gmatch("[^;]+") do | ||
local imageName = mw.text.trim(image) | |||
-- Sanitize logo path - extract filename and remove prefixes | |||
imageName = TemplateHelpers.sanitizeUserInput(imageName, "IMAGE_FILES") | |||
-- Store just the image name, we'll use MediaWiki's renderer | |||
if imageName and imageName ~= "" then | |||
table.insert(images, imageName) | |||
end | |||
end | end | ||
return images | return images | ||
| Line 36: | Line 40: | ||
-- Create single image HTML | -- Create single image HTML | ||
local function createSingleImage( | local function createSingleImage(imageName, maxWidth) | ||
local frame = mw.getCurrentFrame() | |||
-- Use wiki syntax for images via preprocess | |||
local img = frame:preprocess(string.format('[[File:%s|%s]]', imageName, maxWidth)) | |||
return string.format( | return string.format( | ||
'<div class="person-portrait-carousel"> | '<div class="person-portrait-carousel">%s</div>', | ||
img | |||
) | ) | ||
end | end | ||
-- Create dual-image orbital display HTML | -- Create dual-image orbital display HTML with navigation arrows | ||
local function createDualImages(images, maxWidth) | local function createDualImages(images, maxWidth) | ||
local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person") | local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person") | ||
local frame = mw.getCurrentFrame() | |||
-- Use wiki syntax for images through preprocess | |||
local img1 = frame:preprocess(string.format('[[File:%s|%s]]', images[1], maxWidth)) | |||
local img2 = frame:preprocess(string.format('[[File:%s|%s]]', images[2], maxWidth)) | |||
-- Include navigation arrows similar to the carousel, but keep the orbital animation styles | |||
return string.format( | return string.format( | ||
'<div class="person-portrait-carousel">' .. | '<div class="person-portrait-carousel">' .. | ||
'<div class="carousel-container">' .. | '<div class="carousel-container">' .. | ||
'<div class="carousel-nav carousel-prev">◀</div>' .. | |||
'<div class="carousel-images" id="carousel-%s">' .. | '<div class="carousel-images" id="carousel-%s">' .. | ||
'<div class="carousel-item carousel-orbital-1" | '<div class="carousel-item carousel-orbital-1" data-index="1">%s</div>' .. | ||
'<div class="carousel-item carousel-orbital-2" | '<div class="carousel-item carousel-orbital-2" data-index="2">%s</div>' .. | ||
'</div></div></div>', | '</div>' .. | ||
'<div class="carousel-nav carousel-next">▶</div>' .. | |||
'</div></div>', | |||
navId, | navId, | ||
img1, img2 | |||
) | ) | ||
end | end | ||
| Line 65: | Line 80: | ||
local itemsHtml = {} | local itemsHtml = {} | ||
local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person") | local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person") | ||
local frame = mw.getCurrentFrame() | |||
-- Generate carousel items | -- Generate carousel items | ||
for i, | for i, imageName in ipairs(images) do | ||
local class = i == 1 and "carousel-visible" or "carousel-hidden" | 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 | |||
-- Use wiki syntax for images | |||
local img = frame:preprocess(string.format('[[File:%s|%s]]', imageName, maxWidth)) | |||
table.insert(itemsHtml, string.format( | table.insert(itemsHtml, string.format( | ||
'<div class="carousel-item %s" | '<div class="carousel-item %s" data-index="%d">%s</div>', | ||
class, | class, i, img | ||
)) | )) | ||
end | end | ||
| Line 79: | Line 105: | ||
'<div class="person-portrait-carousel">' .. | '<div class="person-portrait-carousel">' .. | ||
'<div class="carousel-container">' .. | '<div class="carousel-container">' .. | ||
'<div class="carousel-nav carousel-prev | '<div class="carousel-nav carousel-prev">◀</div>' .. | ||
'<div class="carousel-images" id="carousel-%s">%s</div>' .. | '<div class="carousel-images" id="carousel-%s">%s</div>' .. | ||
'<div class="carousel-nav carousel-next | '<div class="carousel-nav carousel-next">▶</div>' .. | ||
'</div></div>', | '</div></div>', | ||
navId, table.concat(itemsHtml) | |||
) | ) | ||
end | end | ||
| Line 99: | Line 125: | ||
-- Determine display type based on number of images | -- Determine display type based on number of images | ||
local carouselHtml = "" | |||
if #images == 0 then | if #images == 0 then | ||
return "" -- No images | return "" -- No images, don't create a row | ||
elseif #images == 1 then | elseif #images == 1 then | ||
carouselHtml = createSingleImage(images[1], maxWidth) | |||
elseif #images == 2 then | elseif #images == 2 then | ||
carouselHtml = createDualImages(images, maxWidth) | |||
else | else | ||
carouselHtml = createCarousel(images, maxWidth) | |||
end | end | ||
-- Wrap in a table row to ensure proper separation from the title | |||
return string.format('|-\n| colspan="2" class="person-portrait-row" | %s', carouselHtml) | |||
end | end | ||
Latest revision as of 03:01, 25 August 2025
Documentation for this module may be created at Module:ElementPortraitCarousel/doc
--[[
* Name: ElementPortraitCarousel
* Author: Mark W. Datysgeld
* Description: Element module that renders portrait carousels for Person templates with three display modes based on image count
* Notes: Single image (centered display); dual images (orbital display with animation); multi-image (full carousel with navigation arrows); integrates with Blueprint template system; image sanitization; protected execution with error handling
]]
local p = {}
p.elementName = "portraitCarousel"
-- Load required modules
local ErrorHandling = require('Module:ErrorHandling')
local TemplateHelpers = require('Module:TemplateHelpers')
-- Default configuration
p.defaultConfig = {
maxWidth = "220px",
carouselClass = "person-portrait-carousel"
}
-- 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)
-- Sanitize logo path - extract filename and remove prefixes
imageName = TemplateHelpers.sanitizeUserInput(imageName, "IMAGE_FILES")
-- Store just the image name, we'll use MediaWiki's renderer
if imageName and imageName ~= "" then
table.insert(images, imageName)
end
end
return images
end
-- Create single image HTML
local function createSingleImage(imageName, maxWidth)
local frame = mw.getCurrentFrame()
-- Use wiki syntax for images via preprocess
local img = frame:preprocess(string.format('[[File:%s|%s]]', imageName, maxWidth))
return string.format(
'<div class="person-portrait-carousel">%s</div>',
img
)
end
-- Create dual-image orbital display HTML with navigation arrows
local function createDualImages(images, maxWidth)
local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person")
local frame = mw.getCurrentFrame()
-- Use wiki syntax for images through preprocess
local img1 = frame:preprocess(string.format('[[File:%s|%s]]', images[1], maxWidth))
local img2 = frame:preprocess(string.format('[[File:%s|%s]]', images[2], maxWidth))
-- Include navigation arrows similar to the carousel, but keep the orbital animation styles
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">' ..
'<div class="carousel-item carousel-orbital-1" data-index="1">%s</div>' ..
'<div class="carousel-item carousel-orbital-2" data-index="2">%s</div>' ..
'</div>' ..
'<div class="carousel-nav carousel-next">▶</div>' ..
'</div></div>',
navId,
img1, img2
)
end
-- Create multi-image carousel HTML
local function createCarousel(images, maxWidth)
local itemsHtml = {}
local navId = mw.uri.anchorEncode(mw.title.getCurrentTitle().text or "person")
local frame = mw.getCurrentFrame()
-- Generate carousel items
for i, imageName 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
-- Use wiki syntax for images
local img = frame:preprocess(string.format('[[File:%s|%s]]', imageName, maxWidth))
table.insert(itemsHtml, string.format(
'<div class="carousel-item %s" data-index="%d">%s</div>',
class, i, img
))
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
local carouselHtml = ""
if #images == 0 then
return "" -- No images, don't create a row
elseif #images == 1 then
carouselHtml = createSingleImage(images[1], maxWidth)
elseif #images == 2 then
carouselHtml = createDualImages(images, maxWidth)
else
carouselHtml = createCarousel(images, maxWidth)
end
-- Wrap in a table row to ensure proper separation from the title
return string.format('|-\n| colspan="2" class="person-portrait-row" | %s', carouselHtml)
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