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= | -- 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 .. "}}" | ||
-- | -- Use JSON config or fallback to default instruction text | ||
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, | 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 | 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 | ||
-- Use the centralized NoticeFactory to create the notice | |||
local noticeOptions = { | |||
type = "campaign-js", | |||
local | |||
type = "campaign", | |||
position = "top", | position = "top", | ||
content = bannerContent, | content = bannerContent, | ||
title = titleText, | |||
cssClass = cssClass | cssClass = cssClass | ||
} | } | ||
return WikitextProcessor.createNoticeForJS(noticeOptions) .. ErrorHandling.formatCombinedOutput(context) | |||
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 | return nil -- Return nil if all items were "not applicable" | ||
end | end | ||
else | else | ||
| Line 330: | Line 347: | ||
end | end | ||
-- | -- Determine template mode based on parameter completeness | ||
local | local templateMode | ||
if not hasCustomParameters then | |||
templateMode = "documentation" | |||
elseif hasAllParameters then | |||
templateMode = "complete" | |||
else | |||
templateMode = "partial" | |||
end | |||
-- Store mode | -- Store mode state for rendering | ||
args. | args._template_mode = templateMode | ||
args. | args._show_instructions = (templateMode ~= "complete") | ||
args._campaign_data = campaignData | |||
args._campaign_data = campaignData | |||
-- 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 | ||
if fieldDef.key ~= "title" then | |||
table.insert(fields, { | |||
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 | if templateMode ~= "complete" then | ||
table.insert(fields, { | table.insert(fields, { | ||
key = "usageInstructions", | key = "usageInstructions", | ||
| Line 364: | Line 386: | ||
type = "text" | type = "text" | ||
}) | }) | ||
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 = { | 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. | 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. | 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 = | local depth = getRecursionDepth(frame) | ||
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 = | args = TemplateHelpers.normalizeArgumentCase(args) | ||
args._recursion_depth = tostring(depth + 1) | args._recursion_depth = tostring(depth + 1) | ||
args = Blueprint.runPreprocessors(template, args) | args = Blueprint.runPreprocessors(template, args) | ||
| Line 482: | Line 498: | ||
end | end | ||
local result = TemplateStructure.render(args, structureConfig, template._errorContext) | local result = TemplateStructure.render(args, structureConfig, template._errorContext) | ||