Module:T-Person

Revision as of 13:56, 6 May 2025 by MarkWD (talk | contribs) (// via Wikitext Extension for VSCode)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:T-Person/doc

-- Module:T-Person
-- Blueprint migration of Person template (Phase 1)

local p = {}

-- ==================== Required modules ====================
local Blueprint        = require('Module:LuaTemplateBlueprint')
local TemplateHelpers  = require('Module:TemplateHelpers')
local ErrorHandling    = require('Module:ErrorHandling')
local ConfigRepository = require('Module:ConfigRepository')
local LinkParser       = require('Module:LinkParser')

-- Blueprint default: Module-level cache for lazy-loaded modules
local moduleCache = {}

-- Blueprint default: Lazy module loader
local function lazyRequire(moduleName)
    return function()
        if not moduleCache[moduleName] then
            moduleCache[moduleName] = require(moduleName)
        end
        return moduleCache[moduleName]
    end
end

-- Blueprint default: Modules to lazy load
local CanonicalForms            = lazyRequire('Module:CanonicalForms')
local CountryData               = lazyRequire('Module:CountryData')
local SemanticCategoryHelpers   = lazyRequire('Module:SemanticCategoryHelpers')
local LanguageNormalization     = lazyRequire('Module:NormalizationLanguage')
local socialFooter              = lazyRequire('Module:SocialMedia')
local Achievements              = lazyRequire('Module:AchievementSystem')

-- ==================== Helper Functions ====================
local errorContext = ErrorHandling.createContext('T-Person')

local function extractSemanticValue(fieldValue, fieldName)
    return TemplateHelpers.extractSemanticValue(fieldValue, fieldName, errorContext)
end

-- ================================================================================

-- IMPORTANT! TEMPLATE BLUEPRINT FRAMEWORK INSTRUCTIONS
local template = Blueprint.registerTemplate('Person', {
    features = {
        title              = true,
        logo               = false,
        fields             = true,
        socialMedia        = true,
        semanticProperties = true,
        categories         = true,
        errorReporting     = true,
    }
})

-- Blueprint default: Initialize standard configuration
Blueprint.initializeConfig(template)

-- CONTROL THE VISUAL ORDER OF BLOCKS
template.config.blockSequence = {
    'title',
    'achievementHeader',
    'portraitCarousel',
    'fields',
    'achievementBadges',
    'socialMedia'
}

-- ================================================================================

-- Block: Title
local function renderTitleBlock(args, frame)
    local pageId = mw.title.getCurrentTitle().id
    local cls, name = Achievements().getTitleClass(pageId, frame)
    local aid = ''
    if cls and cls ~= '' then
        aid = cls:gsub('^achievement%-', '')
    end
    return TemplateHelpers.renderTitleBlock(
        args,
        'template-title template-title-person person-template',
        'Person',
        {
            achievementSupport = true,
            achievementClass   = cls or '',
            achievementId      = aid,
            achievementName    = name or ''
        }
    )
end

-- Block: Achievement header
local function renderAchievementHeaderBlock(args, frame)
    local pageId = mw.title.getCurrentTitle().id
    local css, display, aid = Achievements().getTitleAchievement(pageId, frame)
    if css ~= '' and display ~= '' and aid ~= '' then
        return string.format(
            '|-\n! colspan="2" class="achievement-header %s" data-achievement-id="%s" data-achievement-name="%s" | %s',
            aid, aid, display, display
        )
    end
    return ''
end

-- Block: Portrait carousel
local function renderPortraitCarousel(args)
    if not args.portrait or args.portrait == '' then
        return ''
    end
    local images = SemanticCategoryHelpers().splitMultiValueString(
        args.portrait,
        SemanticCategoryHelpers().SEMICOLON_PATTERN
    )
    if #images == 0 then
        return ''
    elseif #images == 1 then
        return string.format(
            '|-\n| colspan="2" class="person-portrait" | [[Image:%s|220px|center]]',
            images[1]
        )
    end
    local out = '|-\n| colspan="2" class="person-portrait-carousel" |'
    out = out .. '<div class="carousel-container">'
    out = out .. '<div class="carousel-nav carousel-prev">&#9664;</div>'
    out = out .. '<div class="carousel-images">'
    for i, img in ipairs(images) do
        local vis  = (i == 1) and 'carousel-visible' or 'carousel-hidden'
        local pos  = ''
        if #images == 2 then
            pos = (i == 1) and 'carousel-orbital-1' or 'carousel-orbital-2'
        else
            if i == 2 then pos = 'carousel-right' end
            if i == #images then pos = 'carousel-left' end
        end
        out = out .. string.format(
            '<div class="carousel-item %s %s" data-index="%d">[[Image:%s|220px|center]]</div>',
            vis, pos, i, img
        )
    end
    out = out .. '</div>'
    out = out .. '<div class="carousel-nav carousel-next">&#9654;</div>'
    out = out .. '</div>'
    return out
end

-- Block: Fields
local function renderFieldsBlock(args, frame)
    local processors = {
        community = function(val)
            return select(1, CanonicalForms().normalize(val, template.config.mappings.community)) or val
        end,
        languages = function(val)
            return LanguageNormalization().formatLanguages(val)
        end,
        country  = TemplateHelpers.normalizeCountries,
        website  = TemplateHelpers.normalizeWebsites,
        soi      = function(val)
            local formatted = string.format('[%s Here]', val)
            return {
                isCompleteHtml = true,
                html           = string.format(TemplateHelpers.FIELD_FORMAT, 'SOI', formatted)
            }
        end,
        userbox  = function(val)
            local pid = mw.title.getCurrentTitle().id
            local ok, box = pcall(function()
                return Achievements().renderAchievementBox(pid, frame)
            end)
            if ok and box and box ~= '' then
                return box
            end
            return val
        end
    }
    return TemplateHelpers.renderFieldsBlock(args, template.config.fields, processors)
end

-- Block: Achievement badges
local function renderAchievementBadgesBlock(args, frame)
    local pid = mw.title.getCurrentTitle().id
    local ok, achmod = pcall(Achievements)
    local parts = { '|-\n| colspan="2" |' }
    if not ok then
        table.insert(parts, '<div class="achievement-badges"></div>')
        return table.concat(parts)
    end
    local types = achmod.loadTypes(frame)
    local defs  = {}
    for _, t in ipairs(types) do defs[t.id] = t end
    local badges = {}
    for _, a in ipairs(achmod.getUserAchievements(pid)) do
        local td = defs[a.type]
        if td and td.type == 'badge' then
            table.insert(badges, { id = a.type, name = td.name or a.type })
        end
    end
    if #badges > 0 then
        table.insert(parts, '<div class="achievement-badges">')
        for _, b in ipairs(badges) do
            table.insert(parts, string.format(
                '<div class="achievement-badge %s" data-achievement-id="%s" data-achievement-name="%s" title="%s"></div>',
                b.id, b.id, b.name, b.name
            ))
        end
        table.insert(parts, '</div>')
    else
        table.insert(parts, '<div class="achievement-badges"></div>')
    end
    return table.concat(parts)
end

-- Register blocks with Blueprint
Blueprint.addBlock(template, 'title', renderTitleBlock)
Blueprint.addBlock(template, 'achievementHeader', renderAchievementHeaderBlock)
Blueprint.addBlock(template, 'portraitCarousel', renderPortraitCarousel)
Blueprint.addBlock(template, 'fields', renderFieldsBlock)
Blueprint.addBlock(template, 'achievementBadges', renderAchievementBadgesBlock)
Blueprint.addBlock(template, 'socialMedia', socialFooter().render)

-- ==================== Preprocessors ====================
Blueprint.addPreprocessor(template, 'setPageIdField')

-- ==================== Main Render Function ====================
function p.render(frame)
    return ErrorHandling.protect(
        errorContext,
        'render',
        function()
            return template.render(frame)
        end,
        ErrorHandling.getMessage('TEMPLATE_RENDER_ERROR'),
        frame
    )
end

return p