Jump to content

Module:Card: Difference between revisions

From The Petit Planet Wiki
Fixed nil value error for field 'text'
No edit summary
 
(31 intermediate revisions by 4 users not shown)
Line 1: Line 1:
local p = {}
local p = {}
local lib = require('Module:Feature')
local lib = require('Module:Feature')
local ne = lib.isNotEmpty
local TemplateData = require('Module:TemplateData')
local TemplateData = require('Module:TemplateData')
local Icon = require('Module:Icon')
local array = require('Module:Array')
 
local CentralData = require('Module:CentralData')


local QUALITY_STARS = {
local allData = CentralData.dataSheets
['1'] = '[[File:1 Star Icon.png|x16px|link=|alt=Quality 1]]',
['2'] = '[[File:2 Stars Icon.png|x16px|link=|alt=Quality 2]]',
['3'] = '[[File:3 Stars Icon.png|x16px|link=|alt=Quality 3]]',
['4'] = '[[File:4 Stars Icon.png|x16px|link=|alt=Quality 4]]',
['5'] = '[[File:5 Stars Icon.png|x16px|link=|alt=Quality 5]]'
}
local PREFIX_ICONS = {
['Instructions: '] = {name='Instructions', type='Item'},
['Diagram: '] = {name='Diagram', type='Item'},
['Recipe: '] = {name='Recipe', type='Item'},
['Formula: '] = {name='Formula', type='Item'},
['Blueprint: '] = {name='Furnishing Blueprint', type='Icon'},
}


-- main template/module parameters
local CARD_ARGS = {
local CARD_ARGS = {
{
{
name='name', alias={1,'character'}, displayName=1,
name = 'name',
status='suggested',
alias = 1,
label='Name',
default = 'Unknown',
description='The name of the image (without the prefix or extension).',
default='Unknown',
example={'Mora', 'Primogem', 'Amber'},
},
{
name='text', alias=2, displayName=2,
label='Card Text',
description='The text below the image.',
example={'100', 'Lv. 1', 'Set'},
},
{
name='multiline_text', type='1',
label='Allow Multiple Lines of Card Text',
trueDescription='allow card text to wrap to a new line if necessary.',
},
{
name='text_size', type='string',
label='Size of Card Text',
description='Adds the "card-text-<text_size>" class, including "small", "smaller".',
},
{
name='quality', displayType='number',
label='Card Quality',
description='The Quality of the item. For light background, add 10. Use 0 for white version. Use multiple numbers for split rarities.',
default='0',
displayDefault='determined based on Image Name; "0" if unknown',
example={'1', '2', '3', '4', '5', '123', '23', '34', '45'},
},
{
name='type', placeholderFor='Module:Icon',
},
{
name='suffix', placeholderFor='Module:Icon',
},
{
name='extension', placeholderFor='Module:Icon',
},
{
name='link', displayType='wiki-page-name',
label='Card Link',
description='The page to link to when image or caption text is clicked.',
displayDefault='same as Image Name',
defaultFrom='name',
example={'Mora', 'Dressing Room', 'Shops', 'Amber'},
},
{
name='link_suffix',
label='Page Link Suffix',
description='Text to append to the Page Link. (Usually used to link to page sections.)',
default='',
example='#Trivia',
},
},
{
{
name='nolink', type='1',
name = 'label',
label='No Link',
alias = {2, 'text', 'count', 'amount', 'x'},
trueDescription='remove default links from card image, icons, and caption.',
displayDefault='"1" if Image Name is default ("Unknown")',
},
},
{
{
name='icon', displayType='content',
name = 'show_label',
label='Icon',
alias = 'show_text',
description='The icon at the top left of the card.',
type = '1',
example={'{{Icon/Element|Pyro|25}}', '[[File:Item Primogem.png|25px]]'},
},
},
{
{
name='icon_style', displayType='string',
name = 'quality',
label='Icon Style',
alias = 'rarity',
description='The custom style of the icon at the top left of the card. Omit the \'\' used for CSS.',
default = 'Common',
example='background: red;',
},
},
{
{
name='icon_right', displayType='content',
name = 'type',
label='Top-Right Icon',
description='Icon on the top right of the card.',
example='{{Icon/Element|Pyro|25}}',
},
},
{
{
name='icon_right_style', displayType='string',
name = 'split',
label='Top-Right Icon Style',
description='The custom style of the icon at the top right of the card. Omit the \'\' used for CSS.',
example='background: red;',
},
},
{
{
name='icon_bottom_left', displayType='content',
name = 'suffix',
label='Bottom-Left Icon',
default = '',
description='Icon on the bottom left of the card.',
example='{{Icon/Element|Pyro|25}}',
},
},
{
{
name='icon_bottom_left_style', displayType='string',
name = 'image',
label='Bottom-Left Icon Style',
description='The custom style of the icon at the bottom left of the card. Omit the \'\' used for CSS.',
example='background: red;',
},
},
{
{
name='icon_bottom_right', displayType='content',
name = 'image_size',
label='Bottom-Right Icon',
alias = {'imageSize', 'size'},
description='Icon on the bottom right of the card.',
example='{{Icon/Element|Pyro|25}}',
},
},
{
{
name='icon_bottom_right_style', displayType='string',
name = 'image_ext',
label='Bottom-Right Icon Style',
alias = {'imageExt', 'ext', 'extension'},
description='The custom style of the icon at the bottom right of the card. Omit the \'\' used for CSS.',
default = 'png',
example='background: red;',
},
},
{
{
name='show_caption', type='1',
name = 'link',
label='Show Caption',
trueDescription='show caption text below the card.',
},
},
{
{
name='caption', displayType='string',
name = 'link_suffix',
label='Caption Text',
default = '',
description='The linked caption text that shows below the card.',
displayDefault='same as Name',
defaultFrom='name',
example={'Apple', 'Rewards'},
},
},
{
{
name='caption_width',
name = 'no_link',
label='Caption Width',
type = '1',
description='Width of the captions. Set to \'auto\' to automatically increase the caption width to prevent it from wrapping in the middle of words.',
displayDefault='same as card width (74px, or 40px for mini cards)',
example={'200px', 'auto'},
},
},
{
{
name='note', displayType='string',
name = 'caption',
label='Caption Note',
description='The unlinked caption text that shows below the linked caption text.',
example='(Bonus reward)',
},
},
{
{
name='refinement', alias='r', displayType='number',
name = 'show_caption',
label='Refinement Rank',
type = '1',
description='The refinement of the weapon. Set to \'1a\' to denote a weapon that cannot be refined.',
example={'1', '2', '3', '4', '5', '1a'},
},
},
{
{
name='ascension', placeholderFor='Module:Icon',
name = 'caption_width',
alias = 'captionWidth',
},
},
{
{
name='constellation', alias='c', displayType='number',
name = 'mini',
label='Constellation Level',
type = '1',
description='Constellation Level of the Character.',
example={'0', '1', '2', '3', '4', '5', '6'},
},
},
{
{
name='outfit', placeholderFor='Module:Icon',
name = 'first',
type = '1',
},
},
{
{
name='stars', type='1',
name = 'up',
label='Show Item Stars',
alias = 'UP',
trueDescription='show the stars of the quality of the item. (Does not work if quality is "0" or split (e.g., "123" or "34").)',
type = '1',
},
},
{
{
name='set', type='1',
name = 'notice',
label='Show Set Icon',
displayDefault='"1" if the card type is "Artifact Set"',
trueDescription='show set icon at the top right corner of the card.',
},
{
name='vol', displayType='number',
label='Book Volume',
description='Volume number of the book (for cards that show books). Sets Card Text and Page Link Suffix to point to the given volume.',
example={'1', '2', '3'},
},
{
name='danger', type='1',
label='Danger',
trueDescription='add Warning icon as the Top-Right Icon.',
},
{
name='mini', type='1',
label='Mini',
trueDescription='show a smaller card. (Not all parameters apply to mini cards.)',
},
{
name='miliastra_wonderland', type='1',
label='Miliastra Wonderland',
trueDescription='Shows styles for Miliastra Wonderland items and cosmetics.',
},
{
name='mobile_list', type='1',
label='Mobile List Item',
trueDescription='show Template:Item-like display on mobile',
},
},
}
}
-- add parameters inherited from Icon for the main image, top-left/right icon, and equipped icon
Icon.addIconArgs(CARD_ARGS, '', '', { -- exclude args already present in CARD_ARGS or that don't apply to the main image
name=false,
link=false,
size=false,
alt=false,
})
Icon.addIconArgs(CARD_ARGS, 'icon_', 'Top-Left Icon ', { -- override to add `element` alias and document automatic icons
name={
alias='element',
description='Name of the icon to display at the top left of the card (without the prefix or extension).',
displayDefault='element icon for characters; furnishing blueprint/recipe/diagram icon if Name is prefixed accordingly; empty otherwise',
example={'Furnishing Blueprint', 'Fruit Paste Bait', 'Anemo'}
},
})
Icon.addIconArgs(CARD_ARGS, 'icon_right_', 'Top-Right Icon ', {
name={
description='Name of the icon to display at the top right of the card (without the prefix or extension).',
example={'Furnishing Blueprint', 'Fruit Paste Bait', 'Anemo'}
},
})
Icon.addIconArgs(CARD_ARGS, 'icon_bottom_left_', 'Bottom-Left Icon ', {
name={
description='Name of the icon to display at the bottom left of the card (without the prefix or extension).',
example={'Furnishing Blueprint', 'Fruit Paste Bait', 'Anemo'}
},
})
Icon.addIconArgs(CARD_ARGS, 'icon_bottom_right_', 'Bottom-Right Icon ', {
name={
description='Name of the icon to display at the bottom right of the card (without the prefix or extension).',
example={'Furnishing Blueprint', 'Fruit Paste Bait', 'Anemo'}
},
})
Icon.addIconArgs(CARD_ARGS, 'equipped_', 'Equipped ', { -- overrides to add `equipped`/`e` aliases and change default size
name={
alias={'equipped', 'e'},
description='Name of the weapon or character to display at the top right of the card (or bottom left, if Constellation Level is present; name does not include the prefix or extension).',
example={'Song of Broken Pines', 'Hu Tao'}
},
size={default='30'},
})


function p.main(frame)
function p.main(frame)
local args = require('Module:Arguments').getArgs(frame, {
local args = require('Module:Arguments').getArgs(frame, {
parentFirst = true,
wrapper = {'Template:Card'},
wrapper = { 'Template:Card' }
parentOnly = false
})
})
return p._main(args, frame)
 
return p._main(args)
end
end


-- apply defaults; load and apply data (e.g., quality, character element)
local function getDataByName(dataType, dataTable, name)
function p._processArgs(args)
local data = dataTable[name]
local out = TemplateData.processArgs(args, CARD_ARGS)
 
if data then
-- set defaults that depend on other arguments
return data
out.defaults.show_caption = lib.isNotEmpty(out.nonDefaults.caption)
out.defaults.nolink = out.name == out.defaults.name and lib.isEmpty(out.nonDefaults.link)
if out.vol ~= nil then
out.defaults.text = 'Vol. ' .. out.vol
out.defaults.link_suffix = '#Vol._' .. out.vol
end
end


-- handle deprecated caption value
local normalizedType = tostring(dataType or ''):lower()
if tostring(out.caption) == '1' then
if normalizedType == 'neighbor' or normalizedType == 'neighbors' then
out.uses_deprecated_params = true
return nil
out.caption = nil
out.show_caption = true
end
end
 
-- handle nolink
for quality, names in pairs(dataTable) do
out.final_link = out.nolink and '' or (out.link .. out.link_suffix)
if type(names) == 'table' then
for _, itemName in ipairs(names) do
-- build args for main card image
if itemName == name then
local mainImageArgs = Icon.extractIconArgs(out)
return {
mainImageArgs.link = out.final_link
quality = quality
mainImageArgs.size = 74
}
end
-- check for prefixes in `name` that correspond to top-left icons
end
-- note: priority of icon specification: `icon` arg > `icon_name` arg > prefix of `name` > character element from data
local baseName, prefix = Icon.stripPrefixes(out.name, PREFIX_ICONS)
if prefix then
-- configure icon if none is specified by `icon_name`,
-- making sure not to change `icon_type` if `icon_name` is explicitly specified
if out.icon_name == out.defaults.icon_name then
local prefixIcon = PREFIX_ICONS[prefix]
out.icon_name = prefixIcon.name
out.icon_type = prefixIcon.type
end
end
mainImageArgs.name = baseName
end
end
 
-- create card image and get type/data
return nil
local card_type, data
end
out.image, card_type, data = Icon.createIcon(mainImageArgs)
 
function p.getTypeAndData(name, requestedType)
-- set defaults based on data
local img_type = requestedType or 'Item'
if data then
local img_data = nil
if card_type == 'Character' then
 
if out.icon_name == out.defaults.icon_name and data.element ~= 'Adaptive' and lib.isNotEmpty(data.element) then
for _, v in ipairs(allData) do
out.icon_name = data.element
local dataType = v[1]
out.icon_type = 'Element'
local dataTable = v[2]
out.icon_link = ''
 
if lib.isEmpty(requestedType) or dataType == requestedType then
local data = getDataByName(dataType, dataTable, name)
 
if data then
img_type = dataType
img_data = data
break
end
end
out.element = data.element or out.element
out.weapon = data.weapon or out.weapon
out.defaults.text = data.name or out.name
out.defaults.text_size = data.text_size or out.text_size
end
end
if card_type == 'Enemy' then
end
if out.icon_name == out.defaults.icon_name and lib.isNotEmpty(data.element) then
 
out.icon_name = data.element
return img_type, img_data
out.icon_type = 'Element'
end
out.icon_link = ''
 
end
local function normalizeBool(value)
out.element = data.element or out.element
return value == true or value == '1' or value == 1 or value == 'yes'
end
 
local function normalizeClassName(value)
return tostring(value or '')
:lower()
:gsub('%s+', '-')
end
 
local function normalizeQuality(quality)
quality = tostring(quality or '')
 
if quality:find(';', 1, true) then
quality = array._main({
quality,
';',
firstItemOnly = 1
})
end
 
if lib.isEmpty(quality) then
quality = 'Common'
end
 
return normalizeClassName(quality)
end
 
local function stripLinkPrefix(value)
value = tostring(value or '')
 
-- Converts "Luca: Myriad Meadows" to "Myriad Meadows".
return mw.text.trim(value:gsub('^.-:%s*', '', 1))
end
 
local function setCaptionWidth(node, width)
if width == 'auto' or lib.isEmpty(width) then
node:addClass('auto-width')
else
node:css('width', width)
end
end
 
-- Generates the second icon for a split card.
local function makeSplitIcon(name, mini)
name = tostring(name or '')
 
local size = mini and 74 or 56
 
return '[[File:' ..
lib.sanitizeFileName(name) ..
' Icon.png|' ..
size .. 'x' .. size ..
'px|link=' ..
name ..
']]'
end
 
function p._processArgs(args)
local out = TemplateData.processArgs(args, CARD_ARGS)
 
-- Basic identity
out.name = out.name or 'Unknown'
out.split = out.split or ''
 
-- Use only a manually supplied type for lookup.
local requestedType = out.nonDefaults
and out.nonDefaults.type
or nil
 
out.type, out.data = p.getTypeAndData(
out.name,
requestedType
)
 
-- Base name for assets.
-- Removes a trailing disambiguation such as "(Currency)".
out.image_name = out.name--:gsub('%s*%([^()]*%)$', '')
 
-- Boolean parameters
out.mini = normalizeBool(out.mini)
out.first = normalizeBool(out.first)
out.up = normalizeBool(out.up)
out.no_link = normalizeBool(out.no_link)
out.show_label = normalizeBool(out.show_label)
out.show_caption = normalizeBool(out.show_caption)
 
-- Image defaults
out.suffix = out.suffix or ''
out.image_ext = out.image_ext or 'png'
 
if lib.isEmpty(out.image_size) then
out.image_size = out.mini and 48 or 70
end
 
if lib.isEmpty(out.suffix) then
out.suffix = ' Icon'
end
 
-- Data-driven defaults
if out.data then
if out.data.name then
out.defaults.label = out.data.name
out.defaults.caption = out.data.name
end
end
if card_type == 'Fish' then
 
if out.icon_name == out.defaults.icon_name and lib.isNotEmpty(data.bait) then
if out.data.title then
out.icon_name = data.bait
out.defaults.caption = out.data.title
out.icon_type = 'Item'
out.icon_link = ''
end
out.prefix = ''
end
end
if card_type == 'Food' then
 
if out.icon_name == out.defaults.icon_name and lib.isNotEmpty(data.effectType) then
if out.data.quality then
out.icon_name = data.effectType
out.defaults.quality = out.data.quality
out.icon_type = 'Icon'
 
out.icon_link = ''
if not (out.nonDefaults and out.nonDefaults.quality) then
out.quality = out.data.quality
end
end
end
if card_type == 'Cosmetic Component' or card_type == 'Cosmetic Set' or data.mode == 'Miliastra Wonderland' then
out.miliastra_wonderland = true
end
if data.quality then
out.defaults.quality = data.quality
end
if data.title then
out.defaults.caption = data.title
end
end
end
end
 
-- set other defaults based on card type
-- Quality / rarity
if card_type == 'Artifact Set' then
out.quality = normalizeQuality(out.quality)
out.defaults.set = true
 
-- Label handling
if out.show_label then
out.label = out.data
and (out.data.name or out.data.title)
or out.name
end
end
 
-- add top-left icon (if not specified by 'icon' wikitext)
if out.label == '0' then
if out.icon == out.defaults.icon then
out.label = nil
local iconImage = Icon.createIcon(out, 'icon_')
iconImage.defaults.size = lib.ternary(out.mini, 15, 20)
-- disable link if nolink is explicitly set, but icon_link is not
if out.nonDefaults.nolink then
iconImage.defaults.link = ''
end
out.icon = iconImage:buildString()
end
end
 
-- add top-right icon (if not specified by 'icon_right' wikitext)
-- Type-specific behavior
if out.icon_right == out.defaults.icon_right then
local lcType = tostring(out.type or ''):lower()
local iconImage = Icon.createIcon(out, 'icon_right_')
 
if lcType == 'neighbor' then
iconImage.defaults.size = lib.ternary(out.mini, 15, 20)
if lib.isEmpty(out.label) then
-- disable link if nolink is explicitly set, but icon_link is not
if out.mini and out.data and out.data.shortName then
if out.nonDefaults.nolink then
out.label = out.data.shortName
iconImage.defaults.link = ''
elseif not out.mini then
out.label = out.name
end
end
end
out.icon_right = iconImage:buildString()
 
end
if tonumber(out.image_size) then
out.image_size = tonumber(out.image_size) + 2
-- add top-right icon (if not specified by 'icon_bottom_left' wikitext)
if out.icon_bottom_left == out.defaults.icon_bottom_left then
local iconImage = Icon.createIcon(out, 'icon_bottom_left_')
iconImage.defaults.size = lib.ternary(out.mini, 15, 20)
-- disable link if nolink is explicitly set, but icon_link is not
if out.nonDefaults.nolink then
iconImage.defaults.link = ''
end
end
out.icon_bottom_left = iconImage:buildString()
end
end
 
-- add top-right icon (if not specified by 'icon_bottom_right' wikitext)
if lcType == 'redirect' then
if out.icon_bottom_right == out.defaults.icon_bottom_right then
out.defaults.link = stripLinkPrefix(out.name)
local iconImage = Icon.createIcon(out, 'icon_bottom_right_')
 
if not (out.nonDefaults and out.nonDefaults.link) then
iconImage.defaults.size = lib.ternary(out.mini, 10, 20)
out.link = stripLinkPrefix(out.name)
-- disable link if nolink is explicitly set, but icon_link is not
if out.nonDefaults.nolink then
iconImage.defaults.link = ''
end
end
out.icon_bottom_right = iconImage:buildString()
end
end
 
-- add equipped icon
-- Caption defaults
if lib.isNotEmpty(out.equipped_name) then
if out.show_caption and lib.isEmpty(out.caption) then
local equippedImage, equippedType = Icon.createIcon(out, 'equipped_')
if out.data then
if equippedType == 'Character' then
out.caption = out.data.title
equippedImage.defaults.size = 50
or out.data.name
equippedImage.defaults.suffix = 'Side Icon'
or out.name
equippedImage.defaults.alt = 'Equipped on ' .. equippedImage.alt
else
else
equippedImage.defaults.alt = 'Equipped with ' .. equippedImage.alt
out.caption = out.name
end
if out.nonDefaults.nolink then
equippedImage.defaults.link = ''
end
end
out.equipped = equippedImage:buildString()
end
end
 
    -- add enemy danger icon
-- Link handling
if out.danger then
if out.link == nil then
        out.icon_right = '[[File:Icon Warning.png|25x25px|link=|alt=Warning icon]]'
out.link = lib.ternary(
        out.icon_right_style = 'top: -8px; right: -10px; z-index: 1'
out.name == 'Unknown',
'',
out.name
)
end
 
out.link_suffix = out.link_suffix or ''
out.final_link = out.link .. out.link_suffix
 
if out.no_link then
out.final_link = ''
end
end
return out
end


local function setCaptionWidth(node, width)
-- Main image filename
if width == 'auto' then
if ne(out.image) then
node:addClass('auto-width')
out.final_file = out.image
else
else
node:css('width', width)
out.final_file =
lib.sanitizeFileName(out.image_name) ..
out.suffix ..
'.' ..
out.image_ext
end
end
-- Main image wikitext
local linkPart = out.no_link
and '|link='
or ('|link=' .. out.final_link)
out.image_wikitext = table.concat({
'[[File:',
out.final_file,
'|',
out.image_size,
'px|alt=',
out.data and (out.data.name or out.data.title)
or out.name,
linkPart,
']]'
})
return out
end
end


function p._main(args, frame)
function p._main(args)
local a = p._processArgs(args)
local a = p._processArgs(args)
local isSplit = ne(a.split)
local node_container = mw.html.create('div'):addClass('card-container')
 
-- Outer container
local node_container = mw.html.create('div')
:addClass('card-container')
 
if isSplit then
node_container:addClass('card-split-card')
end
 
if a.mini then
if a.mini then
node_container:addClass('mini-card')
node_container:addClass('mini-card')
end
end
 
if a.miliastra_wonderland then
-- Wrapper and body
node_container:addClass('miliastra-wonderland')
end
if a.text and tonumber((tostring(a.text):gsub(',', ''):gsub('[kKmMbBtT%%]$', '')))~=nil then
local mult = a.text:gsub('^.-([kKmMbBtT%%])$', '%1')
a.text = lib.thousandsSeparator((tostring(a.text):gsub(',', ''):gsub('[kKmMbBtT%%]$', ''))) .. (mult == a.text and '' or mult)
end
-- create two wrapper divs for the main card body
-- (required for CSS positioning and rounded corners, respectively)
local node_card = node_container:tag('span')
local node_card = node_container:tag('span')
:addClass('card-wrapper')
:addClass('card-wrapper')
:tag('span')
:tag('span')
:addClass('card-body')
:addClass('card-body')
 
local node_image = node_card:tag('span')
-- Galaxy class on body
if not a.mini and ne(a.galaxy) then
node_card:addClass(
'card-galaxy-' .. normalizeClassName(a.galaxy)
)
end
 
-- Main image container
local imageContainer = node_card:tag('span')
:addClass('card-image-container')
:addClass('card-image-container')
if lib.isNotEmpty(a.quality) then
:addClass('card-quality-' .. a.quality)
if tonumber(a.quality)==nil then node_image:addClass('card-quality-' .. string.lower(a.quality:gsub('%D', ''):gsub(' ', '%-'))) end
 
node_image:addClass('card-quality-' .. string.lower(a.quality:gsub(' ', '%-')))
-- Split or regular image
end
if isSplit then
if lib.isNotEmpty(a.weapon) then node_image:addClass('card-weapon-' .. string.lower(a.weapon:gsub(' ', '%-'))) end
imageContainer:addClass('card-split-image')
if lib.isNotEmpty(a.element) then node_image:addClass('card-element-' .. string.lower(a.element:gsub(' ', '%-'))) end
 
node_image:tag('span')
-- First item: top-left half
:wikitext(a.image:buildString())
imageContainer:tag('span')
:addClass('card-split-half')
if lib.isNotEmpty(a.icon) then
:addClass('card-split-half-a')
:wikitext(a.image_wikitext)
 
-- Second item: bottom-right half
imageContainer:tag('span')
:addClass('card-split-half')
:addClass('card-split-half-b')
:wikitext(makeSplitIcon(a.split, a.mini))
else
imageContainer:wikitext(a.image_wikitext)
end
 
-- Notice label
if not a.mini and ne(a.notice) then
node_card:tag('span')
:addClass('card-notice-label')
:wikitext(a.notice)
end
 
-- First modifier
if a.first then
node_card:tag('span')
:addClass('card-modifier')
:addClass('card-modifier-first')
:wikitext(
'[[File:Icon Item First.png|x25px|link=|alt=First]]'
)
end
 
-- UP modifier
if a.up then
node_card:tag('span')
node_card:tag('span')
:addClass('card-icon')
:addClass('card-modifier')
:cssText(a.icon_style)
:addClass('card-modifier-up')
:wikitext(a.icon)
:wikitext(
'[[File:Icon Item UP.png|x25px|link=|alt=UP]]'
)
end
end
 
if a.mini then
-- Label below the image
if lib.isNotEmpty(a.text) and tonumber((a.text:gsub(',', ''):gsub('[kKmMbBtT%%]$', ''))) ~= nil then
if ne(a.label) then
node_card:tag('span'):addClass('card-text'):wikitext(a.text)
node_container:tag('span')
end
:addClass('card-label')
if lib.isNotEmpty(a.icon_right) then
:wikitext(a.label)
node_card:tag('span')
:addClass('card-icon-right')
:cssText(a.icon_right_style)
:wikitext(a.icon_right)
end
else
if a.set then
node_card:tag('span')
:addClass('card-set-container')
:tag('span')
:addClass('icon')
:wikitext('[[File:Set.svg|14px|link=|alt=Artifact set]]')
end
if a.stars then
local starsImage = QUALITY_STARS[a.quality]
if starsImage then
node_card:tag('span')
:addClass('card-stars')
:tag('span')
:wikitext(starsImage)
end
end
if lib.isNotEmpty(a.icon_right) then
node_card:tag('span')
:addClass('card-icon-right')
:cssText(a.icon_right_style)
:wikitext(a.icon_right)
end
if lib.isNotEmpty(a.icon_bottom_left) then
node_card:tag('span')
:addClass('card-icon-bottom-left')
:cssText(a.icon_bottom_left_style)
:wikitext(a.icon_bottom_left)
end
if lib.isNotEmpty(a.icon_bottom_right) then
node_card:tag('span')
:addClass('card-icon-bottom-right')
:cssText(a.icon_bottom_right_style)
:wikitext(a.icon_bottom_right)
end
if lib.isNotEmpty(a.equipped) then
node_card:tag('span')
:addClass('card-equipped')
:wikitext(a.equipped)
end
if lib.isNotEmpty(a.text) then
local node_text = node_card:tag('span')
:addClass('card-text')
:addClass('card-font')
:wikitext(a.text)
if a.text_size then
node_text:addClass('card-text-' .. a.text_size)
end
if a.multiline_text then
node_text:addClass('multi-line')
end
end
if lib.isNotEmpty(a.refinement) then
node_card:tag('span')
:addClass('card-refinement')
:addClass('refine-' .. a.refinement)
:wikitext(' R', (a.refinement:gsub('a', ''))) -- extra parens to take only first value returned from gsub
end
if lib.isNotEmpty(a.constellation) then
node_card:tag('span')
:addClass('card-constellation')
:addClass('constellation-' .. a.constellation)
:wikitext(' C', a.constellation)
node_card:addClass('card-with-constellation')
end
end
end
 
-- Caption
if a.show_caption and lib.isNotEmpty(a.caption) then
-- Split captions are opt-in and use spans instead of list items.
if a.show_caption and isSplit then
local node_caption = node_container:tag('span')
local node_caption = node_container:tag('span')
:addClass('card-caption')
:addClass('card-caption')
:wikitext(' ', a.nolink and a.caption or ('[[' .. a.final_link .. '|' .. a.caption .. ']]'))
:addClass('card-split-caption')
 
local list = node_caption:tag('span')
:addClass('card-split-caption-list')
 
list:tag('span')
:addClass('card-split-caption-item')
:wikitext('[[' .. a.name .. ']]')
 
list:tag('span')
:addClass('card-split-caption-item')
:wikitext('[[' .. a.split .. ']]')
 
setCaptionWidth(node_caption, a.caption_width)
setCaptionWidth(node_caption, a.caption_width)
end
 
elseif a.show_caption and ne(a.caption) then
if lib.isNotEmpty(a.note) then
local node_caption = node_container:tag('span')
local node_note = node_container:tag('span')
:addClass('card-caption')
:addClass('card-caption')
:wikitext(a.note)
 
setCaptionWidth(node_note, a.caption_width)
if a.no_link or lib.isEmpty(a.final_link) then
end
node_caption:wikitext(a.caption)
else
if a.mobile_list then
node_caption:wikitext(
if a.text ~= '&mdash;' or a.caption or a.note then
'[[' .. a.final_link .. '|' .. a.caption .. ']]'
local node_mobile_text = node_card
)
:tag('span')
:addClass('card-mobile-text')
if a.nolink then
node_mobile_text:wikitext(' ', a.caption)
else
node_mobile_text:wikitext(' [[', a.final_link, '|', a.caption, ']]')
end
if tonumber((a.text:gsub(',', ''))) ~= nil then
node_mobile_text:wikitext(' ×')
end
if (a.caption or ''):gsub('&shy;', '') ~= (a.text or ''):gsub('&shy;', '') and a.text ~= '&mdash;' then
node_mobile_text:wikitext(' ', a.text)
end
if a.note then
node_mobile_text:wikitext(' (', a.note, ')')
end
end
end
end
 
setCaptionWidth(node_caption, a.caption_width)
if a.uses_deprecated_params then
node_container:wikitext('[[Category:Pages Using Deprecated Template Parameters]]')
end
end


return node_container
return tostring(node_container)
end
end


function p.templateData(frame)
function p.templateData(frame)
return TemplateData.templateData(CARD_ARGS, {
return TemplateData.templateData(CARD_ARGS, {
description="This template is used to create an item display that is similar to the items in the Inventory in-game. Text can be shown below the item's image, background and stars can match the quality, refinement, icons, and set can be added.\n\nIf the image name matches an existing Item, then the quality will automatically be set.\n\nIf the image name matches an existing Character of Weapon, then the format will automatically change to match that type item.",
description = 'Creates an item-style card with image, quality, label, split images, modifiers, and optional data-driven icons.',
format='inline'
format = 'inline'
}, frame)
}, frame)
end
end

Latest revision as of 10:53, 22 October 2026

Module documentation
This Lua module is used primarily on pages in the Main namespace. It can be found on 2,428 pages, or on about 60.61% of those pages.
Edits to this Lua module will be widely noticed. To avoid large-scale disruption and unnecessary server load, any changes to this Lua module should be tested in its sandbox or testcases subpages. The tested changes can then be added to this page in one single edit.
Please consider discussing any changes in the wiki's Discord.

This module implements {{Card}}.

To update, see: Module:CentralData.

local p = {}

local lib = require('Module:Feature')
local ne = lib.isNotEmpty
local TemplateData = require('Module:TemplateData')
local array = require('Module:Array')
local CentralData = require('Module:CentralData')

local allData = CentralData.dataSheets

local CARD_ARGS = {
	{
		name = 'name',
		alias = 1,
		default = 'Unknown',
	},
	{
		name = 'label',
		alias = {2, 'text', 'count', 'amount', 'x'},
	},
	{
		name = 'show_label',
		alias = 'show_text',
		type = '1',
	},
	{
		name = 'quality',
		alias = 'rarity',
		default = 'Common',
	},
	{
		name = 'type',
	},
	{
		name = 'split',
	},
	{
		name = 'suffix',
		default = '',
	},
	{
		name = 'image',
	},
	{
		name = 'image_size',
		alias = {'imageSize', 'size'},
	},
	{
		name = 'image_ext',
		alias = {'imageExt', 'ext', 'extension'},
		default = 'png',
	},
	{
		name = 'link',
	},
	{
		name = 'link_suffix',
		default = '',
	},
	{
		name = 'no_link',
		type = '1',
	},
	{
		name = 'caption',
	},
	{
		name = 'show_caption',
		type = '1',
	},
	{
		name = 'caption_width',
		alias = 'captionWidth',
	},
	{
		name = 'mini',
		type = '1',
	},
	{
		name = 'first',
		type = '1',
	},
	{
		name = 'up',
		alias = 'UP',
		type = '1',
	},
	{
		name = 'notice',
	},
}

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrapper = {'Template:Card'},
		parentOnly = false
	})

	return p._main(args)
end

local function getDataByName(dataType, dataTable, name)
	local data = dataTable[name]

	if data then
		return data
	end

	local normalizedType = tostring(dataType or ''):lower()
	if normalizedType == 'neighbor' or normalizedType == 'neighbors' then
		return nil
	end

	for quality, names in pairs(dataTable) do
		if type(names) == 'table' then
			for _, itemName in ipairs(names) do
				if itemName == name then
					return {
						quality = quality
					}
				end
			end
		end
	end

	return nil
end

function p.getTypeAndData(name, requestedType)
	local img_type = requestedType or 'Item'
	local img_data = nil

	for _, v in ipairs(allData) do
		local dataType = v[1]
		local dataTable = v[2]

		if lib.isEmpty(requestedType) or dataType == requestedType then
			local data = getDataByName(dataType, dataTable, name)

			if data then
				img_type = dataType
				img_data = data
				break
			end
		end
	end

	return img_type, img_data
end

local function normalizeBool(value)
	return value == true or value == '1' or value == 1 or value == 'yes'
end

local function normalizeClassName(value)
	return tostring(value or '')
		:lower()
		:gsub('%s+', '-')
end

local function normalizeQuality(quality)
	quality = tostring(quality or '')

	if quality:find(';', 1, true) then
		quality = array._main({
			quality,
			';',
			firstItemOnly = 1
		})
	end

	if lib.isEmpty(quality) then
		quality = 'Common'
	end

	return normalizeClassName(quality)
end

local function stripLinkPrefix(value)
	value = tostring(value or '')

	-- Converts "Luca: Myriad Meadows" to "Myriad Meadows".
	return mw.text.trim(value:gsub('^.-:%s*', '', 1))
end

local function setCaptionWidth(node, width)
	if width == 'auto' or lib.isEmpty(width) then
		node:addClass('auto-width')
	else
		node:css('width', width)
	end
end

-- Generates the second icon for a split card.
local function makeSplitIcon(name, mini)
	name = tostring(name or '')

	local size = mini and 74 or 56

	return '[[File:' ..
		lib.sanitizeFileName(name) ..
		' Icon.png|' ..
		size .. 'x' .. size ..
		'px|link=' ..
		name ..
		']]'
end

function p._processArgs(args)
	local out = TemplateData.processArgs(args, CARD_ARGS)

	-- Basic identity
	out.name = out.name or 'Unknown'
	out.split = out.split or ''

	-- Use only a manually supplied type for lookup.
	local requestedType = out.nonDefaults
		and out.nonDefaults.type
		or nil

	out.type, out.data = p.getTypeAndData(
		out.name,
		requestedType
	)

	-- Base name for assets.
	-- Removes a trailing disambiguation such as "(Currency)".
	out.image_name = out.name--:gsub('%s*%([^()]*%)$', '')

	-- Boolean parameters
	out.mini = normalizeBool(out.mini)
	out.first = normalizeBool(out.first)
	out.up = normalizeBool(out.up)
	out.no_link = normalizeBool(out.no_link)
	out.show_label = normalizeBool(out.show_label)
	out.show_caption = normalizeBool(out.show_caption)

	-- Image defaults
	out.suffix = out.suffix or ''
	out.image_ext = out.image_ext or 'png'

	if lib.isEmpty(out.image_size) then
		out.image_size = out.mini and 48 or 70
	end

	if lib.isEmpty(out.suffix) then
		out.suffix = ' Icon'
	end

	-- Data-driven defaults
	if out.data then
		if out.data.name then
			out.defaults.label = out.data.name
			out.defaults.caption = out.data.name
		end

		if out.data.title then
			out.defaults.caption = out.data.title
		end

		if out.data.quality then
			out.defaults.quality = out.data.quality

			if not (out.nonDefaults and out.nonDefaults.quality) then
				out.quality = out.data.quality
			end
		end
	end

	-- Quality / rarity
	out.quality = normalizeQuality(out.quality)

	-- Label handling
	if out.show_label then
		out.label = out.data
			and (out.data.name or out.data.title)
			or out.name
	end

	if out.label == '0' then
		out.label = nil
	end

	-- Type-specific behavior
	local lcType = tostring(out.type or ''):lower()

	if lcType == 'neighbor' then
		if lib.isEmpty(out.label) then
			if out.mini and out.data and out.data.shortName then
				out.label = out.data.shortName
			elseif not out.mini then
				out.label = out.name
			end
		end

		if tonumber(out.image_size) then
			out.image_size = tonumber(out.image_size) + 2
		end
	end

	if lcType == 'redirect' then
		out.defaults.link = stripLinkPrefix(out.name)

		if not (out.nonDefaults and out.nonDefaults.link) then
			out.link = stripLinkPrefix(out.name)
		end
	end

	-- Caption defaults
	if out.show_caption and lib.isEmpty(out.caption) then
		if out.data then
			out.caption = out.data.title
				or out.data.name
				or out.name
		else
			out.caption = out.name
		end
	end

	-- Link handling
	if out.link == nil then
		out.link = lib.ternary(
			out.name == 'Unknown',
			'',
			out.name
		)
	end

	out.link_suffix = out.link_suffix or ''
	out.final_link = out.link .. out.link_suffix

	if out.no_link then
		out.final_link = ''
	end

	-- Main image filename
	if ne(out.image) then
		out.final_file = out.image
	else
		out.final_file =
			lib.sanitizeFileName(out.image_name) ..
			out.suffix ..
			'.' ..
			out.image_ext
	end

	-- Main image wikitext
	local linkPart = out.no_link
		and '|link='
		or ('|link=' .. out.final_link)

	out.image_wikitext = table.concat({
		'[[File:',
		out.final_file,
		'|',
		out.image_size,
		'px|alt=',
		out.data and (out.data.name or out.data.title)
			or out.name,
		linkPart,
		']]'
	})

	return out
end

function p._main(args)
	local a = p._processArgs(args)
	local isSplit = ne(a.split)

	-- Outer container
	local node_container = mw.html.create('div')
		:addClass('card-container')

	if isSplit then
		node_container:addClass('card-split-card')
	end

	if a.mini then
		node_container:addClass('mini-card')
	end

	-- Wrapper and body
	local node_card = node_container:tag('span')
		:addClass('card-wrapper')
		:tag('span')
			:addClass('card-body')

	-- Galaxy class on body
	if not a.mini and ne(a.galaxy) then
		node_card:addClass(
			'card-galaxy-' .. normalizeClassName(a.galaxy)
		)
	end

	-- Main image container
	local imageContainer = node_card:tag('span')
		:addClass('card-image-container')
		:addClass('card-quality-' .. a.quality)

	-- Split or regular image
	if isSplit then
		imageContainer:addClass('card-split-image')

		-- First item: top-left half
		imageContainer:tag('span')
			:addClass('card-split-half')
			:addClass('card-split-half-a')
			:wikitext(a.image_wikitext)

		-- Second item: bottom-right half
		imageContainer:tag('span')
			:addClass('card-split-half')
			:addClass('card-split-half-b')
			:wikitext(makeSplitIcon(a.split, a.mini))
	else
		imageContainer:wikitext(a.image_wikitext)
	end

	-- Notice label
	if not a.mini and ne(a.notice) then
		node_card:tag('span')
			:addClass('card-notice-label')
			:wikitext(a.notice)
	end

	-- First modifier
	if a.first then
		node_card:tag('span')
			:addClass('card-modifier')
			:addClass('card-modifier-first')
			:wikitext(
				'[[File:Icon Item First.png|x25px|link=|alt=First]]'
			)
	end

	-- UP modifier
	if a.up then
		node_card:tag('span')
			:addClass('card-modifier')
			:addClass('card-modifier-up')
			:wikitext(
				'[[File:Icon Item UP.png|x25px|link=|alt=UP]]'
			)
	end

	-- Label below the image
	if ne(a.label) then
		node_container:tag('span')
			:addClass('card-label')
			:wikitext(a.label)
	end

	-- Caption
	-- Split captions are opt-in and use spans instead of list items.
	if a.show_caption and isSplit then
		local node_caption = node_container:tag('span')
			:addClass('card-caption')
			:addClass('card-split-caption')

		local list = node_caption:tag('span')
			:addClass('card-split-caption-list')

		list:tag('span')
			:addClass('card-split-caption-item')
			:wikitext('[[' .. a.name .. ']]')

		list:tag('span')
			:addClass('card-split-caption-item')
			:wikitext('[[' .. a.split .. ']]')

		setCaptionWidth(node_caption, a.caption_width)

	elseif a.show_caption and ne(a.caption) then
		local node_caption = node_container:tag('span')
			:addClass('card-caption')

		if a.no_link or lib.isEmpty(a.final_link) then
			node_caption:wikitext(a.caption)
		else
			node_caption:wikitext(
				'[[' .. a.final_link .. '|' .. a.caption .. ']]'
			)
		end

		setCaptionWidth(node_caption, a.caption_width)
	end

	return tostring(node_container)
end

function p.templateData(frame)
	return TemplateData.templateData(CARD_ARGS, {
		description = 'Creates an item-style card with image, quality, label, split images, modifiers, and optional data-driven icons.',
		format = 'inline'
	}, frame)
end

function p.syntax(frame)
	return TemplateData.syntax(CARD_ARGS, frame)
end

return p