Module:T-Campaign: Difference between revisions

// via Wikitext Extension for VSCode
// via Wikitext Extension for VSCode
 
(48 intermediate revisions by the same user not shown)
Line 1: Line 1:
-- T-Campaign.lua
-- Module:T-Campaign.lua
-- Generic campaign template that dynamically loads campaign data from JSON files
-- Generic campaign template that dynamically loads campaign data from JSON files
-- Usage: {{#invoke:T-Campaign|render|campaign_name=ASP2025}}
-- Usage: {{#invoke:T-Campaign|render|campaign_name=NAME}}


local p = {}
local p = {}
Line 10: Line 10:
local DatasetLoader = require('Module:DatasetLoader')
local DatasetLoader = require('Module:DatasetLoader')
local WikitextProcessor = require('Module:WikitextProcessor')
local WikitextProcessor = require('Module:WikitextProcessor')
local TemplateHelpers = require('Module:TemplateHelpers')
local TemplateStructure = require('Module:TemplateStructure')


-- Register the Campaign template with Blueprint
-- Register the Campaign template with Blueprint
Line 42: Line 44:


template.config.blocks = template.config.blocks or {}
template.config.blocks = template.config.blocks or {}
-- Helper function: Check for "not applicable" values
local function isNotApplicable(value)
    if not value or type(value) ~= "string" then
        return false
    end
    local lowerVal = value:lower():match("^%s*(.-)%s*$")
    return lowerVal == "n/a" or lowerVal == "na" or lowerVal == "none" or lowerVal == "no" or lowerVal == "false"
end


-- Helper function: Tokenize semicolon-separated strings for instructions
-- Helper function: Tokenize semicolon-separated strings for instructions
Line 85: Line 96:
     templateCall = templateCall .. "}}"
     templateCall = templateCall .. "}}"
      
      
     -- Build instruction content
     -- Use JSON config or fallback to default instruction text
     table.insert(output, "'''READ CAREFULLY'''. These are temporary instructions that will appear only until all parameters outlined here have been filled. Choose 'Edit source' at any time and edit the code below if it already exists. It can also be added manually to any page:")
     local headerText = (campaignData.instructions and campaignData.instructions.header_text)
        or "'''READ CAREFULLY'''. These are temporary instructions that will appear only until all parameters outlined here have been filled with data or have 'N/A' in them if the field is not applicable. Choose 'Edit source' at any time and edit the code below if it already exists. It can also be added manually to any page:"
   
    local parameterIntro = (campaignData.instructions and campaignData.instructions.parameter_intro)
        or "'''Available Parameters:'''"
   
    -- Build instruction content with configurable text
    table.insert(output, headerText)
     table.insert(output, "")
     table.insert(output, "")
     table.insert(output, "<pre>" .. templateCall .. "</pre>")
     table.insert(output, "<pre>" .. templateCall .. "</pre>")
     table.insert(output, "")
     table.insert(output, "")
     table.insert(output, "'''Available Parameters:'''")
     table.insert(output, parameterIntro)
      
      
     if campaignData.field_definitions then
     if campaignData.field_definitions then
Line 131: Line 149:
      
      
     return campaignName
     return campaignName
end
-- Helper function: Get recursion depth from frame arguments
local function getRecursionDepth(frame)
    local frameArgs = frame.args or {}
    local parentArgs = (frame:getParent() and frame:getParent().args) or {}
   
    return tonumber(frameArgs._recursion_depth or parentArgs._recursion_depth) or 0
end
end


Line 146: Line 172:
         local banner = args._campaign_data.banner
         local banner = args._campaign_data.banner
         local bannerContent = banner.content or ""
         local bannerContent = banner.content or ""
         local cssClass = banner.css_class or "campaign-banner"
         local titleText = template.config.constants.title or "Campaign"
       
        -- Combine generic notice-box class with specific campaign class
        local cssClass = "notice-box"
        if banner.css_class and banner.css_class ~= "" then
            cssClass = cssClass .. " " .. banner.css_class
        end
          
          
         if bannerContent == "" then
         if bannerContent == "" then
Line 153: Line 185:
         end
         end
          
          
         local placeholderValues = {
         -- Use the centralized NoticeFactory to create the notice
            CAMPAIGN_NAME = args._campaign_data.defaults.title or "Campaign"
         local noticeOptions = {
        }
             type = "campaign-js",
       
        bannerContent = WikitextProcessor.processContentForFrontend(bannerContent, placeholderValues, context)
       
         local noticeData = {
             type = "campaign",
             position = "top",
             position = "top",
             content = bannerContent,
             content = bannerContent,
            title = titleText,
             cssClass = cssClass
             cssClass = cssClass
         }
         }
          
          
         local success, result = pcall(function()
         return WikitextProcessor.createNoticeForJS(noticeOptions) .. ErrorHandling.formatCombinedOutput(context)
            return string.format(
                '<div style="display:none" class="notice-data" data-notice-type="%s" data-notice-position="%s" data-notice-content="%s" data-notice-css="%s"></div>',
                mw.text.encode(noticeData.type),
                mw.text.encode(noticeData.position),
                mw.text.encode(noticeData.content),
                mw.text.encode(noticeData.cssClass)
            )
        end)
       
        if success then
            return result .. ErrorHandling.formatCombinedOutput(context)
        else
            ErrorHandling.addError(context, 'campaignBanner', 'Data attribute creation failed', tostring(result), false)
            return ErrorHandling.formatCombinedOutput(context)
        end
     end
     end
}
}
Line 242: Line 255:
-- Generic field processor that handles different data types
-- Generic field processor that handles different data types
local function processFieldValue(value, fieldType)
local function processFieldValue(value, fieldType)
    if isNotApplicable(value) then
        return nil
    end
     if type(value) == "table" then
     if type(value) == "table" then
         if #value > 0 then
         if #value > 0 then
Line 265: Line 282:
         for item in value:gmatch("[^;]+") do
         for item in value:gmatch("[^;]+") do
             local trimmed = item:match("^%s*(.-)%s*$") -- trim whitespace
             local trimmed = item:match("^%s*(.-)%s*$") -- trim whitespace
             if trimmed and trimmed ~= "" then
             if trimmed and trimmed ~= "" and not isNotApplicable(trimmed) then
                 table.insert(items, '<span class="campaign-token">' .. trimmed .. '</span>')
                 table.insert(items, '<span class="campaign-token">' .. trimmed .. '</span>')
             end
             end
Line 272: Line 289:
             return table.concat(items, ' ')
             return table.concat(items, ' ')
         else
         else
             return tostring(value)
             return nil -- Return nil if all items were "not applicable"
         end
         end
     else
     else
Line 330: Line 347:
     end
     end
      
      
     -- FRAGILE: Mode determination logic - affects instruction display and field rendering
     -- Determine template mode based on parameter completeness
     local isPureDocumentation = not hasCustomParameters
     local templateMode
     local isPartialMode = hasCustomParameters and not hasAllParameters
    if not hasCustomParameters then
     local isCompleteMode = hasCustomParameters and hasAllParameters
        templateMode = "documentation"
     local showInstructions = isPureDocumentation or isPartialMode
     elseif hasAllParameters then
        templateMode = "complete"
     else
        templateMode = "partial"
     end
      
      
     -- Store mode flags for use in rendering
     -- Store mode state for rendering
     args._documentation_mode = isPureDocumentation
     args._template_mode = templateMode
     args._partial_mode = isPartialMode
     args._show_instructions = (templateMode ~= "complete")
    args._complete_mode = isCompleteMode
     args._campaign_data = campaignData
    args._show_instructions = showInstructions
     args._campaign_data = campaignData -- Store for usage instruction generation
      
      
     -- Defaults are only used for parameter documentation, not content rendering
     -- Defaults are only used for parameter documentation, not content rendering
Line 350: Line 369:
     -- Always show campaign content fields (they'll show placeholder text when empty)
     -- Always show campaign content fields (they'll show placeholder text when empty)
     for _, fieldDef in ipairs(campaignData.field_definitions) do
     for _, fieldDef in ipairs(campaignData.field_definitions) do
         table.insert(fields, {
         -- CRITICAL: Skip 'title' as it is not a content field
            key = fieldDef.key,
        if fieldDef.key ~= "title" then
            label = fieldDef.label,
            table.insert(fields, {
            type = fieldDef.type
                key = fieldDef.key,
        })
                label = fieldDef.label,
                type = fieldDef.type
            })
        end
     end
     end
      
      
     -- Add usage instructions in documentation and partial modes
     -- Add usage instructions in documentation and partial modes
     if isPureDocumentation or isPartialMode then
     if templateMode ~= "complete" then
         table.insert(fields, {
         table.insert(fields, {
             key = "usageInstructions",
             key = "usageInstructions",
Line 364: Line 386:
             type = "text"
             type = "text"
         })
         })
        -- Set a dummy value so the field gets processed
         args.usageInstructions = "documentation"
         args.usageInstructions = "documentation"
     end
     end
Line 375: Line 396:
     end
     end
      
      
     -- Add campaign-specific category
     -- Add campaign-specific category, defaulting to template_id
     template.config.categories.base = {campaignName}
    local category_value = campaignData.category or campaignData.template_id
     template.config.categories.base = {category_value}
      
      
     return args
     return args
Line 393: Line 415:
     else
     else
         -- Show placeholder for empty fields in documentation and partial modes
         -- Show placeholder for empty fields in documentation and partial modes
         if args._documentation_mode or args._partial_mode then
         if args._template_mode ~= "complete" then
             return "''Please see usage instructions above to customize this field.''"
             return "''Please see usage instructions above to customize this field.''"
         end
         end
Line 408: Line 430:
     else
     else
         -- Fallback if no campaign data available
         -- Fallback if no campaign data available
         if args._documentation_mode or args._partial_mode then
         if args._template_mode ~= "complete" then
             return "''Campaign introduction will appear here from JSON defaults.''"
             return "''Campaign introduction will appear here from JSON defaults.''"
         end
         end
Line 430: Line 452:
     template.current_frame = frame
     template.current_frame = frame
      
      
     local depth = 0
     local depth = getRecursionDepth(frame)
    if frame.args and frame.args._recursion_depth then
        depth = tonumber(frame.args._recursion_depth) or 0
    elseif frame:getParent() and frame:getParent().args and frame:getParent().args._recursion_depth then
        depth = tonumber(frame:getParent().args._recursion_depth) or 0
    end
      
      
     if depth > 3 then
     if depth > 3 then
Line 457: Line 474:
     for k, v in pairs(frameArgs) do args[k] = v end
     for k, v in pairs(frameArgs) do args[k] = v end
      
      
     args = require('Module:TemplateHelpers').normalizeArgumentCase(args)
     args = TemplateHelpers.normalizeArgumentCase(args)
      
      
     args._recursion_depth = tostring(depth + 1)
     args._recursion_depth = tostring(depth + 1)
      
      
    local Blueprint = require('Module:LuaTemplateBlueprint')
     args = Blueprint.runPreprocessors(template, args)
     args = Blueprint.runPreprocessors(template, args)
      
      
Line 482: Line 498:
     end
     end
      
      
    local TemplateStructure = require('Module:TemplateStructure')
     local result = TemplateStructure.render(args, structureConfig, template._errorContext)
     local result = TemplateStructure.render(args, structureConfig, template._errorContext)