Module:ElementPortraitCarousel: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
 
(5 intermediate revisions by the same user not shown)
Line 1: Line 1:
--[[  
--[[
* Module:ElementPortraitCarousel
* Name: ElementPortraitCarousel
* Renders a portrait carousel for person templates with 3 display modes:
* Author: Mark W. Datysgeld
* 1. Single image - Simple centered display
* Description: Element module that renders portrait carousels for Person templates with three display modes based on image count
* 2. Dual images - Orbital display with animation
* 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
* 3. Multi-image - Full carousel with navigation arrows
]]
*
* Integrates with Blueprint template system.
]]


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 21: Line 19:
     carouselClass = "person-portrait-carousel"
     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
-- Split portrait field by semicolons
Line 52: Line 29:
     for image in portrait:gmatch("[^;]+") do
     for image in portrait:gmatch("[^;]+") do
         local imageName = mw.text.trim(image)
         local imageName = mw.text.trim(image)
         local imageUrl = formatImageUrl(imageName)
         -- Sanitize logo path - extract filename and remove prefixes
         if imageUrl and imageUrl ~= "" then
        imageName = TemplateHelpers.sanitizeUserInput(imageName, "IMAGE_FILES")
             table.insert(images, {
        -- Store just the image name, we'll use MediaWiki's renderer
                name = imageName,
         if imageName and imageName ~= "" then
                url = imageUrl
             table.insert(images, imageName)
            })
         end
         end
     end
     end
Line 64: Line 40:


-- Create single image HTML
-- Create single image HTML
local function createSingleImage(imageData, maxWidth)
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 src="%s" style="max-width:%s;" alt="Portrait" />' ..
         img
        '</div>',
         imageData.url, maxWidth
     )
     )
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">&#9664;</div>' ..
         '<div class="carousel-images" id="carousel-%s">' ..
         '<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-1" data-index="1">%s</div>' ..
         '<div class="carousel-item carousel-orbital-2" data-index="2"><img src="%s" alt="Portrait 2" /></div>' ..
         '<div class="carousel-item carousel-orbital-2" data-index="2">%s</div>' ..
         '</div></div></div>',
        '</div>' ..
         '<div class="carousel-nav carousel-next">&#9654;</div>' ..
        '</div></div>',
         navId,
         navId,
         images[1].url, images[2].url
         img1, img2
     )
     )
end
end
Line 93: 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, imageData in ipairs(images) do
     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
         -- Add positioning classes for 3+ images
Line 103: Line 91:
             class = class .. " carousel-left"
             class = class .. " carousel-left"
         end
         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" data-index="%d"><img src="%s" alt="Portrait %d" /></div>',
             '<div class="carousel-item %s" data-index="%d">%s</div>',
             class, i, imageData.url, i
             class, i, img
         ))
         ))
     end
     end
Line 134: 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
                 return createSingleImage(images[1], maxWidth)
                 carouselHtml = createSingleImage(images[1], maxWidth)
             elseif #images == 2 then
             elseif #images == 2 then
                 return createDualImages(images, maxWidth)
                 carouselHtml = createDualImages(images, maxWidth)
             else
             else
                 return createCarousel(images, maxWidth)
                 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