Jump to content

Module:LuaTemplatePerson: Difference between revisions

From ICANNWiki
No edit summary
// via Wikitext Extension for VSCode
Tag: Reverted
Line 14: Line 14:
local TemplateStructure    = require('Module:TemplateStructure')
local TemplateStructure    = require('Module:TemplateStructure')
local LanguageNormalization = require('Module:LanguageNormalization')
local LanguageNormalization = require('Module:LanguageNormalization')
local Achievements          = require('Module:Achievements')


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 120: Line 121:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
local function renderTitleBlock(args)
local function renderTitleBlock(args)
     return string.format('! colspan="2" class="template-title template-title-person" | %s',  
    -- Get achievement class (if any) for this user
         "Person")
    local username = args.name or ""
    local achievementClass = Achievements.getTitleClass(username)
    local classes = "template-title template-title-person person-template"
   
    if achievementClass ~= "" then
        classes = classes .. " " .. achievementClass
    end
   
     return string.format('! colspan="2" class="%s" | %s',  
         classes, "Person")
end
end


Line 213: Line 223:
             elseif key == "soi" then
             elseif key == "soi" then
                 value = string.format("[%s Here]", value)
                 value = string.format("[%s Here]", value)
            elseif key == "userbox" then
                -- Replace user-provided userbox value with achievement box
                local username = args.name or ""
                local achievementBox = Achievements.renderAchievementBox(username)
               
                -- If there are achievements for this user, use those instead
                if achievementBox ~= "" then
                    value = achievementBox
                end
             end
             end
              
              
Line 292: Line 311:
         }
         }
     }
     }
   
    -- Track this page for achievement display cache purging
    local pageName = mw.title.getCurrentTitle().fullText
    if args.name and args.name ~= "" then
        Achievements.trackPage(pageName)
    end
      
      
     return TemplateStructure.render(args, config) .. "\n" .. computeCategories(args)
     return TemplateStructure.render(args, config) .. "\n" .. computeCategories(args)

Revision as of 23:42, 28 March 2025

Documentation for this module may be created at Module:LuaTemplatePerson/doc

-- Template:Person is a module for rendering person profiles with a carousel for multiple images,
-- supporting various normalizations (countries, links, etc.) and integrating social media.
-- The module dynamically assigns categories based on community and country.

local p = {}

-- Dependencies
local CanonicalForms        = require('Module:CanonicalForms')
local countryNormalization  = require('Module:CountryNormalization')
local RegionalMappingICANN  = require('Module:RegionalMappingICANN')
local linkParser            = require('Module:LinkParser')
local MultiCountryDisplay   = require('Module:MultiCountryDisplay')
local socialFooter          = require('Module:SocialMedia')
local TemplateStructure     = require('Module:TemplateStructure')
local LanguageNormalization = require('Module:LanguageNormalization')
local Achievements          = require('Module:Achievements')

--------------------------------------------------------------------------------
-- Configuration and Constants
--------------------------------------------------------------------------------
local Config = {
    communityMapping = {
        {canonical = "ICANN Community", 
         synonyms = {"icann community", "icann", "community"}, 
         category = "[[Category:ICANN Community]]"},
        {canonical = "IG Community", 
         synonyms = {"ig community", "ig", "internet governance"}, 
         category = "[[Category:IG Community]]"},
        {canonical = "ICANN Staff", 
         synonyms = {"icann staff", "staff"}, 
         category = "[[Category:ICANN Staff]]"},
        {canonical = "Former ICANN Staff", 
         synonyms = {"former icann staff", "former staff", "ex-staff"}, 
         category = "[[Category:Former ICANN Staff]]"}
    },
    fields = {
        {key="community", label="Community"},
        {key="affiliation", label="ICANN group"},
        {key="organization", label="Organization"},
        {key="region", label="Region"},
        {key="country", label="Country"},
        {key="languages", label="Languages"},
        {key="website", label="Website"},
        {key="soi", label="SOI"},
        {key="userbox", label="Achievements"}
    },
    patterns = {
        itemDelimiter=";%s*",
        websitePattern="^https?://[^%s]+"
    }
}

--------------------------------------------------------------------------------
-- Helper Functions
--------------------------------------------------------------------------------
-- Split semicolon-separated values into an array
local function splitSemicolonValues(value)
    if not value or value == "" then return {} end
    local items = {}
    for item in string.gmatch(value, "[^;]+") do
        local trimmed = item:match("^%s*(.-)%s*$")
        if trimmed and trimmed ~= "" then
            table.insert(items, trimmed)
        end
    end
    return items
end

-- Gets the first non-empty value from a list of equivalent keys
local function getFieldValue(args, field)
    -- For fields with multiple equivalent keys
    if field.keys then
        for _, key in ipairs(field.keys) do
            if args[key] and args[key] ~= "" then
                return key, args[key]
            end
        end
        return nil, nil
    end
    -- For single-key fields
    return field.key, (args[field.key] and args[field.key] ~= "") and args[field.key] or nil
end

-- Process multiple language entries
local function normalizeLanguages(value)
    if not value or value == "" then return "" end
    local languages = splitSemicolonValues(value)
    
    if #languages > 1 then
        local listItems = {}
        for _, lang in ipairs(languages) do
            table.insert(listItems, string.format("<li>%s</li>", lang))
        end
        return string.format("<ul class=\"template-list template-list-language\" style=\"margin:0; padding-left:1em;\">%s</ul>", table.concat(listItems, ""))
    end
    return languages[1] or ""
end

-- Process multiple country entries handled by the MultiCountryDisplay module

-- Process multiple website entries
local function normalizeWebsites(value)
    if not value or value == "" then return "" end
    local websites = splitSemicolonValues(value)
    
    if #websites > 1 then
        local listItems = {}
        for _, site in ipairs(websites) do
            local formattedLink = string.format("[%s %s]", site, linkParser.strip(site))
            table.insert(listItems, string.format("<li>%s</li>", formattedLink))
        end
        return string.format("<ul class=\"template-list template-list-website\" style=\"margin:0; padding-left:1em;\">%s</ul>", table.concat(listItems, ""))
    elseif #websites == 1 then
        return string.format("[%s %s]", websites[1], linkParser.strip(websites[1]))
    end
    return ""
end

--------------------------------------------------------------------------------
-- Block Rendering Functions
--------------------------------------------------------------------------------
local function renderTitleBlock(args)
    -- Get achievement class (if any) for this user
    local username = args.name or ""
    local achievementClass = Achievements.getTitleClass(username)
    local classes = "template-title template-title-person person-template"
    
    if achievementClass ~= "" then
        classes = classes .. " " .. achievementClass
    end
    
    return string.format('! colspan="2" class="%s" | %s', 
        classes, "Person")
end

-- Process portrait field (semicolon-separated images) into carousel
local function renderPortraitCarousel(args)
    if not args.portrait or args.portrait == "" then
        return ""
    end
    
    local imageFiles = splitSemicolonValues(args.portrait)
    if #imageFiles == 0 then
        return ""
    end
    
    -- If only one image, just display it regularly
    if #imageFiles == 1 then
        return string.format("|-\n| colspan=\"2\" class=\"person-portrait\" | [[Image:%s|220px|center]]", imageFiles[1])
    end
    
    -- Start building the carousel for multiple images
    local output = "|-\n| colspan=\"2\" class=\"person-portrait-carousel\" |"
    
    -- Add the carousel container
    output = output .. '<div class="carousel-container">'
    
    -- Add navigation arrows if more than one image
    if #imageFiles > 1 then
        output = output .. '<div class="carousel-nav carousel-prev">&#9664;</div>'
    end
    
    -- Start images container
    output = output .. '<div class="carousel-images">'
    
    -- Add each image as a carousel item
    for i, imageFile in ipairs(imageFiles) do
        local visibility = i == 1 and "carousel-visible" or "carousel-hidden"
        local position = ""
        
        -- Special handling for exactly 2 images (orbital layout)
        if #imageFiles == 2 then
            if i == 1 then 
                position = "carousel-orbital-1" -- First image in orbital position
            else
                position = "carousel-orbital-2" -- Second image in orbital position
            end
        else
            -- Original logic for 3+ images
            if i == 2 and #imageFiles > 1 then position = "carousel-right" end
            if i == #imageFiles and #imageFiles > 2 then position = "carousel-left" end
        end
        
        output = output .. string.format(
            '<div class="carousel-item %s %s" data-index="%d">[[Image:%s|220px|center]]</div>',
            visibility, position, i, imageFile
        )
    end
    
    -- Close images container
    output = output .. '</div>'
    
    -- Add next button if more than one image
    if #imageFiles > 1 then
        output = output .. '<div class="carousel-nav carousel-next">&#9654;</div>'
    end
    
    -- Close carousel container
    output = output .. '</div>'
    
    return output
end

local function renderFieldsBlock(args)
    local out = {}
    for _, field in ipairs(Config.fields) do
        local key, value = getFieldValue(args, field)
        if value then
            -- Process specific field types
            if key == "community" then
                value = select(1, CanonicalForms.normalize(value, Config.communityMapping)) or value
            elseif key == "languages" then
                value = LanguageNormalization.formatLanguages(value)
            elseif key == "country" then
                -- Each country will get its own globe emoji based on its region
                value = MultiCountryDisplay.formatCountries(value)
            elseif key == "website" then
                value = normalizeWebsites(value)
            elseif key == "region" then
                -- For now, we just display the region as provided
                -- Later we'll auto-derive it from country
                value = value
            elseif key == "soi" then
                value = string.format("[%s Here]", value)
            elseif key == "userbox" then
                -- Replace user-provided userbox value with achievement box
                local username = args.name or ""
                local achievementBox = Achievements.renderAchievementBox(username)
                
                -- If there are achievements for this user, use those instead
                if achievementBox ~= "" then
                    value = achievementBox
                end
            end
            
            table.insert(out, string.format("|-\n| '''%s''':\n| %s", field.label, value))
        end
    end
    return table.concat(out, "\n")
end

--------------------------------------------------------------------------------
-- Category Assignment
--------------------------------------------------------------------------------
local function computeCategories(args)
    local cats = {}
    
    -- Community categories
    if args.community and args.community ~= "" then
        local community = select(1, CanonicalForms.normalize(args.community, Config.communityMapping))
        if community then
            for _, group in ipairs(Config.communityMapping) do
                if group.canonical == community and group.category then
                    table.insert(cats, group.category)
                    break
                end
            end
        end
    end
    
    -- Country category
    if args.country and args.country ~= "" then
        local normalizedCountries = MultiCountryDisplay.getCountriesForCategories(args.country)
        for _, countryName in ipairs(normalizedCountries) do
            table.insert(cats, string.format("[[Category:%s]]", countryName))
        end
    end
    
    -- Region categories
    if args.region and args.region ~= "" then
        local regions = splitSemicolonValues(args.region)
        for _, region in ipairs(regions) do
            local trimmedRegion = region:match("^%s*(.-)%s*$")
            if trimmedRegion and trimmedRegion ~= "" then
                table.insert(cats, string.format("[[Category:%s]]", trimmedRegion))
            end
        end
    end
    
    return table.concat(cats, "\n")
end

--------------------------------------------------------------------------------
-- Main Render Function
--------------------------------------------------------------------------------
function p.render(frame)
    local args = frame:getParent().args or {}
    
    -- Process region information once, early in the template rendering
    if args.country and (not args.region or args.region == "") then
        -- Auto-derive region from country if not provided
        local regions = RegionalMappingICANN.getRegionsForCountries(args.country)
        if #regions > 0 then
            args.region = table.concat(regions, " and ")
        end
    elseif args.region and args.region ~= "" then
        -- Normalize region names using the dedicated function
        local regions = RegionalMappingICANN.normalizeRegions(args.region)
        if #regions > 0 then
            args.region = table.concat(regions, " and ")
        end
    end
    
    -- Configure and render template
    local config = {
        blocks = {
            function(a) return renderTitleBlock(a) end,
            function(a) return renderPortraitCarousel(a) end,
            function(a) return renderFieldsBlock(a) end,
            function(a) return socialFooter.render(a) or "" end
        }
    }
    
    -- Track this page for achievement display cache purging
    local pageName = mw.title.getCurrentTitle().fullText
    if args.name and args.name ~= "" then
        Achievements.trackPage(pageName)
    end
    
    return TemplateStructure.render(args, config) .. "\n" .. computeCategories(args)
end

return p