Jump to content

Module:Sandbox/Stevium: Difference between revisions

From The Petit Planet Wiki
Testing subtitle
No edit summary
Line 1: Line 1:
-- Imported from: https://defaultloadout.wiki.gg/wiki/Module:Infobox
local p = {}
-- version 0.1.11
local lib = require('Module:Feature')
local TemplateData = require('Module:TemplateData')
local Icon = require('Module:Icon')


--------------------------------------
-- User settings, you can modify these
--------------------------------------


-- if you want to not always use divs in your wiki (as opposed to tables), you can change this default
local QUALITY_STARS = {
-- just remember to change it back each time you update from the main "branch" on the support wiki!
['1'] = '[[File:1 Star Icon.png|x16px|link=|alt=Quality 1]]',
-- you can also control it per infobox with `|useDivs=yes` or `|useDivs=no`
['2'] = '[[File:2 Stars Icon.png|x16px|link=|alt=Quality 2]]',
local USE_DIVS = true -- `false` or `true`
['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'},
}


-- default value to show if a param is missing in some but not all tabs.
-- main template/module parameters
-- set to `nil` (not in quotes) to remove such rows altogether in the tabs where they're missing
local CARD_ARGS = {
local TABBED_NONEXIST = nil -- `''` or `nil` or `'N/A'` etc. Don't put nil in quotes.
{
name='name', alias={1,'character'}, displayName=1,
status='suggested',
label='Name',
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',
label='No Link',
trueDescription='remove default links from card image, icons, and caption.',
displayDefault='"1" if Image Name is default ("Unknown")',
},
{
name='icon', displayType='content',
label='Icon',
description='The icon at the top left of the card.',
example={'{{Icon/Element|Pyro|25}}', '[[File:Item Primogem.png|25px]]'},
},
{
name='icon_style', displayType='string',
label='Icon Style',
description='The custom style of the icon at the top left of the card. Omit the \'\' used for CSS.',
example='background: red;',
},
{
name='icon_right', displayType='content',
label='Top-Right Icon',
description='Icon on the top right of the card.',
example='{{Icon/Element|Pyro|25}}',
},
{
name='icon_right_style', displayType='string',
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',
label='Bottom-Left Icon',
description='Icon on the bottom left of the card.',
example='{{Icon/Element|Pyro|25}}',
},
{
name='icon_bottom_left_style', displayType='string',
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',
label='Bottom-Right Icon',
description='Icon on the bottom right of the card.',
example='{{Icon/Element|Pyro|25}}',
},
{
name='icon_bottom_right_style', displayType='string',
label='Bottom-Right Icon Style',
description='The custom style of the icon at the bottom right of the card. Omit the \'\' used for CSS.',
example='background: red;',
},
{
name='show_caption', type='1',
label='Show Caption',
trueDescription='show caption text below the card.',
},
{
name='caption', displayType='string',
label='Caption Text',
description='The linked caption text that shows below the card.',
displayDefault='same as Name',
defaultFrom='name',
example={'Apple', 'Rewards'},
},
{
name='caption_width',
label='Caption Width',
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',
label='Caption Note',
description='The unlinked caption text that shows below the linked caption text.',
example='(Bonus reward)',
},
{
name='refinement', alias='r', displayType='number',
label='Refinement Rank',
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='constellation', alias='c', displayType='number',
label='Constellation Level',
description='Constellation Level of the Character.',
example={'0', '1', '2', '3', '4', '5', '6'},
},
{
name='outfit', placeholderFor='Module:Icon',
},
{
name='stars', type='1',
label='Show Item Stars',
trueDescription='show the stars of the quality of the item. (Does not work if quality is "0" or split (e.g., "123" or "34").)',
},
{
name='set', type='1',
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'},
})


---------------------------------------------------------------------------
-- Do not modify anything below this line unless you know what you're doing
---------------------------------------------------------------------------
local h = {}
local p = {}
local hooks = {}
function p.arraymap(frame)
-- a lua implementation of Page Forms' arraymap
local args = h.overwrite()
local items = h.split(args[1], args[2] or ',')
for i, item in ipairs(items) do
items[i] = args[4]:gsub(args[3], item)
end
return table.concat(items, args[5] or ',')
end
function p.preprocess(frame)
return frame:preprocess(frame.args[1] or frame:getParent().args[1])
end


function p.main(frame)
function p.main(frame)
h.registerHooks()
local args = require('Module:Arguments').getArgs(frame, {
h.increment()
parentFirst = true,
local args = h.overwrite()
wrapper = { 'Template:Card' }
local sep = args.sep or ','
})
h.castArgs(args, sep)
return p._main(args, frame)
if h.castBool(args.setmainimage or 'yes') then
h.setMainImage(args.images[1])
end
return h.makeInfobox(args, sep)
end
end


function h.registerHooks()
-- apply defaults; load and apply data (e.g., quality, character element)
if not mw.title.new('Module:Infobox/Hooks').exists then return end
function p._processArgs(args)
hooks = require('Module:Infobox/Hooks')
local out = TemplateData.processArgs(args, CARD_ARGS)
end
 
-- set defaults that depend on other arguments
function h.runHook(key, ...)
out.defaults.show_caption = lib.isNotEmpty(out.nonDefaults.caption)
if hooks[key] then
out.defaults.nolink = out.name == out.defaults.name and lib.isEmpty(out.nonDefaults.link)
hooks[key](...)
if out.vol ~= nil then
out.defaults.text = 'Vol. ' .. out.vol
out.defaults.link_suffix = '#Vol._' .. out.vol
end
end
end


function h.increment()
-- handle deprecated caption value
-- optional use of VariablesLua for better compatibility
if tostring(out.caption) == '1' then
local VariablesLua = mw.ext.VariablesLua
out.uses_deprecated_params = true
if VariablesLua ~= nil then
out.caption = nil
h.counter = VariablesLua.var('DRUID_INFOBOX_ID', 0) + 1
out.show_caption = true
VariablesLua.vardefine('DRUID_INFOBOX_ID', h.counter)
return
end
end
local res
-- try to fall back to normal Variables
-- handle nolink
res, h.counter = pcall(
out.final_link = out.nolink and '' or (out.link .. out.link_suffix)
function()
return mw.getCurrentFrame():callParserFunction('#var', {'DRUID_INFOBOX_ID', 0}) + 1
-- build args for main card image
local mainImageArgs = Icon.extractIconArgs(out)
mainImageArgs.link = out.final_link
mainImageArgs.size = 74
-- check for prefixes in `name` that correspond to top-left icons
-- 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
if res then
mw.getCurrentFrame():callParserFunction('#vardefine', {'DRUID_INFOBOX_ID', h.counter})
else
-- else use a random number so at least there's some unique id
h.counter = math.random(100000000000000000) -- random integer
end
end
end
 
-- create card image and get type/data
function h.castArgs(args, sep)
local card_type, data
h.runHook('onCastArgsStart', args, sep, args.kind)
out.image, card_type, data = Icon.createIcon(mainImageArgs)
args.tabs = h.split(args.tabs or args.image_labels, sep)
args.images = h.getImages(args, sep)
-- set defaults based on data
args.sections = h.split(args.sections, sep)
if data then
for _, section in ipairs(args.sections) do
if card_type == 'Character' then
if h.castBool(args[section .. '_isdata']) then
if out.icon_name == out.defaults.icon_name and data.element ~= 'Adaptive' and lib.isNotEmpty(data.element) then
args[section .. 'Data'] = args[section]
out.icon_name = data.element
args[section] = section .. 'Data'
out.icon_type = 'Element'
args[section .. 'Data_nolabel'] = 'true' -- will be cast later
out.icon_link = ''
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
if card_type == 'Enemy' then
if out.icon_name == out.defaults.icon_name and lib.isNotEmpty(data.element) then
out.icon_name = data.element
out.icon_type = 'Element'
out.icon_link = ''
end
out.element = data.element or out.element
end
if card_type == 'Fish' then
if out.icon_name == out.defaults.icon_name and lib.isNotEmpty(data.bait) then
out.icon_name = data.bait
out.icon_type = 'Item'
out.icon_link = ''
end
out.prefix = ''
end
if card_type == 'Food' then
if out.icon_name == out.defaults.icon_name and lib.isNotEmpty(data.effectType) then
out.icon_name = data.effectType
out.icon_type = 'Icon'
out.icon_link = ''
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
end
args[section] = h.split(args[section], sep)
if data.title then
args[section .. '_tabs'] = h.split(args[section .. '_tabs'], sep)
out.defaults.caption = data.title
if #args.tabs > 0 and #args[section .. '_tabs'] > 0 then
error(('You cannot specify |tabs= and |%s= at the same time, please pick one'):format(section .. '_tabs'))
end
end
end
end
if args.useDivs then
USE_DIVS = h.castBool(args.useDivs)
-- set other defaults based on card type
if card_type == 'Artifact Set' then
out.defaults.set = true
end
end
-- this would be in the outer scope, but we're hiding it
h.entityType = USE_DIVS and 'div' or 'table' -- key of h.htmlEntities
-- add top-left icon (if not specified by 'icon' wikitext)
h.runHook('onCastArgsEnd', args, sep, args.kind)
if out.icon == out.defaults.icon then
h.focusTab(args)
local iconImage = Icon.createIcon(out, 'icon_')
end
 
iconImage.defaults.size = lib.ternary(out.mini, 15, 20)
function h.getImages(args, sep)
-- disable link if nolink is explicitly set, but icon_link is not
if args.image and not args.images then
if out.nonDefaults.nolink then
args.images = args.image
iconImage.defaults.link = ''
end
out.icon = iconImage:buildString()
end
-- add top-right icon (if not specified by 'icon_right' wikitext)
if out.icon_right == out.defaults.icon_right then
local iconImage = Icon.createIcon(out, 'icon_right_')
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_right = iconImage:buildString()
end
end
if args.images then
return h.split(args.images, sep)
-- 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
out.icon_bottom_left = iconImage:buildString()
end
end
if not args.tabs then return {} end
local ret = {}
-- add top-right icon (if not specified by 'icon_bottom_right' wikitext)
for _, key in ipairs(args.tabs) do
if out.icon_bottom_right == out.defaults.icon_bottom_right then
if args[key .. '_image'] then
local iconImage = Icon.createIcon(out, 'icon_bottom_right_')
ret[#ret+1] = args[key .. '_image']
iconImage.defaults.size = lib.ternary(out.mini, 10, 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_right = iconImage:buildString()
end
end
return ret
end
-- add equipped icon
 
if lib.isNotEmpty(out.equipped_name) then
function h.setMainImage(file)
local equippedImage, equippedType = Icon.createIcon(out, 'equipped_')
if h.counter > 1 then return end
if equippedType == 'Character' then
if not file then return end
equippedImage.defaults.size = 50
local fileText = file:gsub('.-:', '')
equippedImage.defaults.suffix = 'Side Icon'
fileText = fileText:gsub('^([^|%]]+).*', '%1')
equippedImage.defaults.alt = 'Equipped on ' .. equippedImage.alt
-- setmainimage is guaranteed to exist on wiki.gg but may not exist on other wikis
-- it's not a crucial piece of functionality so we'll fail silently if it doesn't exist
pcall(function() mw.getCurrentFrame():callParserFunction{
name = '#setmainimage',
args = { fileText },
} end)
end
 
function h.focusTab(args)
local hasFocusedTab = false
if not args.tabs[1] then return end
for i, tab in ipairs(args.tabs) do
if h.castBool(args[tab .. '_focused']) then
hasFocusedTab = true
args[tab .. '_focused'] = true -- cast to bool
else
else
args[tab .. '_focused'] = false
equippedImage.defaults.alt = 'Equipped with ' .. equippedImage.alt
end
if out.nonDefaults.nolink then
equippedImage.defaults.link = ''
end
end
out.equipped = equippedImage:buildString()
end
    -- add enemy danger icon
if out.danger then
        out.icon_right = '[[File:Icon Warning.png|25x25px|link=|alt=Warning icon]]'
        out.icon_right_style = 'top: -8px; right: -10px; z-index: 1'
end
end
if hasFocusedTab then return end
args[args.tabs[1] .. '_focused'] = true
return out
end
end


function h.makeInfobox(args, sep)
local function setCaptionWidth(node, width)
local out = mw.html.create(h.getTag('container'))
if width == 'auto' then
:addClass('druid-infobox')
node:addClass('auto-width')
:addClass('druid-container')
else
:addClass('noexcerpt')
node:css('width', width)
:addClass(args.class) -- warning: class can be nil, don't concat anything
:attr('id', args.id or ('druid-container-' .. h.counter))
h.runHook('onMakeOutputStart', out, args)
if args.kind then out:addClass('druid-container-' .. h.escape(args.kind)) end
h.printTitle(out, args)
h.printSubtitle(out, args)
h.printImages(out, args.images, args)
for _, section in ipairs(args.sections) do
-- cannot begin tagging here because we don't know if any applicable args are present
local cols = args[section .. '_columns']
local makeSection = cols and h.makeGridSection or h.makeSection
out:node(makeSection(section, args[section], args, tonumber(cols)))
end
end
h.runHook('onMakeOutputEnd', out, args)
return out
end
end


function h.printTitle(out, args)
function p._main(args, frame)
local tabs = args.tabs
local a = p._processArgs(args)
if not tabs or #tabs == 0 then
h.printSimpleTitle(out, args)
local node_container = mw.html.create('div'):addClass('card-container')
return
if a.mini then
node_container:addClass('mini-card')
end
end
if not h.hasComplexData('title', tabs, args) then
h.printSimpleTitle(out, args)
if a.miliastra_wonderland then
return
node_container:addClass('miliastra-wonderland')
end
end
local node = h.printTitleWrapper(out)
h.printTabbedDataItem(node, 'title', tabs, args)
if a.text and tonumber((tostring(a.text):gsub(',', ''):gsub('[kKmMbBtT%%]$', '')))~=nil then
end
local mult = a.text:gsub('^.-([kKmMbBtT%%])$', '%1')
 
a.text = lib.thousandsSeparator((tostring(a.text):gsub(',', ''):gsub('[kKmMbBtT%%]$', ''))) .. (mult == a.text and '' or mult)
function h.printSimpleTitle(out, args)
if args.title then
local node = h.printTitleWrapper(out)
node:wikitext(args.title)
end
end
end
 
-- create two wrapper divs for the main card body
function h.printTitleWrapper(out)
-- (required for CSS positioning and rounded corners, respectively)
return out:tag(h.getTag('titleOuter'))
local node_card = node_container:tag('span')
:tag(h.getTag('titleInner'))
:addClass('card-wrapper')
:addClass('druid-title')
:tag('span')
:attr('colspan', 2)
:addClass('card-body')
end
 
local node_image = node_card:tag('span')
function h.printSubtitle(out, args)
:addClass('card-image-container')
if args.subtitle and args.subtitle ~= '' then
if lib.isNotEmpty(a.quality) then
out:tag(h.getTag('titleOuter'))
if tonumber(a.quality)==nil then node_image:addClass('card-quality-' .. string.lower(a.quality:gsub('%D', ''):gsub(' ', '%-'))) end
:tag(h.getTag('titleInner'))
node_image:addClass('card-quality-' .. string.lower(a.quality:gsub(' ', '%-')))
:addClass('druid-subtitle')
end
:attr('colspan', 2)
if lib.isNotEmpty(a.weapon) then node_image:addClass('card-weapon-' .. string.lower(a.weapon:gsub(' ', '%-'))) end
:wikitext(args.subtitle)
if lib.isNotEmpty(a.element) then node_image:addClass('card-element-' .. string.lower(a.element:gsub(' ', '%-'))) end
node_image:tag('span')
:wikitext(a.image:buildString())
if lib.isNotEmpty(a.icon) then
node_card:tag('span')
:addClass('card-icon')
:cssText(a.icon_style)
:wikitext(a.icon)
end
end
end
function h.printTabbedDataItem(node, item, tabs, args)
-- hasData isn't used in the title case but we will need to track this
-- when we're printing section data later on
-- so we'll just track it always
local hasData = false
for i, label in ipairs(tabs) do
local div = node:tag('div')
:addClass('druid-toggleable-data')
:addClass('druid-toggleable')
:attr('data-druid', h.counter .. '-' .. i)
:attr('data-druid-tab-key', label)
if h.getTabbedContent(args, label, item) then
hasData = true
div:wikitext('\n\n' .. h.getTabbedContent(args, label, item))
div:addClass('druid-toggleable-data-nonempty')
else
div:addClass('druid-toggleable-data-empty')
end
if args[label .. '_focused'] then div:addClass('focused') end
if a.mini then
end
if lib.isNotEmpty(a.text) and tonumber((a.text:gsub(',', ''):gsub('[kKmMbBtT%%]$', ''))) ~= nil then
return hasData
node_card:tag('span'):addClass('card-text'):wikitext(a.text)
end
 
function h.printImages(out, images, args)
if #images == 0 and #args.tabs == 0 then return end
-- burden is on the user to format this as an image. this should be done in the infobox template,
-- with something like |image={{#if:{{{image|}}}|[[File:{{{image|}}}{{!}}300px{{!}}link=]]}}
local td = out:tag(h.getTag('section'))
:addClass('druid-section-container')
:tag(h.getTag('cell'))
:attr('colspan', 2)
local tabs = args.tabs
local tabTexts = h.getImageTabTexts(tabs, images, args)
h.printTabs(td, tabs, tabTexts, false, args)
if #images == 0 then return end
if #images == 1 then
td:addClass('druid-main-image')
:wikitext(images[1])
if args.caption then
td:tag('div')
:addClass('druid-main-image-caption')
:wikitext(args.caption)
end
end
return
if lib.isNotEmpty(a.icon_right) then
end
node_card:tag('span')
td:addClass('druid-main-images')
:addClass('card-icon-right')
local imagesContainer = td:tag('div')
:cssText(a.icon_right_style)
:addClass('druid-main-images-files')
:wikitext(a.icon_right)
for i, image in ipairs(images) do
local container = imagesContainer:tag('div')
:addClass('druid-main-images-file')
:addClass('druid-toggleable')
:attr('data-druid', h.counter .. '-' .. i)
:wikitext(image)
:attr('data-druid-tab-key', tabs[i])
local labelDisplay
local labelKey = tabs[i]
local caption = args.caption
if labelKey then
labelDisplay = args[labelKey .. '_label'] or labelKey
caption = args[labelKey .. '_caption'] or caption
else
labelDisplay = 'Image ' .. i
end
end
caption = args[labelDisplay .. '_caption'] or caption
else
if caption then
if a.set then
container:tag('div')
node_card:tag('span')
:addClass('druid-main-images-caption')
:addClass('card-set-container')
:wikitext(caption)
:tag('span')
:addClass('icon')
:wikitext('[[File:Set.svg|14px|link=|alt=Artifact set]]')
end
end
if labelKey and args[labelKey .. '_focused'] then
if a.stars then
container:addClass('focused')
local starsImage = QUALITY_STARS[a.quality]
if starsImage then
node_card:tag('span')
:addClass('card-stars')
:tag('span')
:wikitext(starsImage)
end
end
end
end
if lib.isNotEmpty(a.icon_right) then
end
node_card:tag('span')
 
:addClass('card-icon-right')
function h.getImageTabTexts(tabs, images, args)
:cssText(a.icon_right_style)
if #tabs == 0 and #images <= 1 then return {} end
:wikitext(a.icon_right)
local texts = {}
local i = 1
while images[i] or tabs[i] do
if tabs[i] then
texts[i] = args[tabs[i] .. '_label'] or tabs[i]
else
texts[i] = 'Image ' .. i
end
end
i = i + 1
if lib.isNotEmpty(a.icon_bottom_left) then
end
node_card:tag('span')
return texts
:addClass('card-icon-bottom-left')
end
:cssText(a.icon_bottom_left_style)
 
:wikitext(a.icon_bottom_left)
function h.printTabs(td, tabs, texts, isSection, args)
if #texts == 0 then return end
local container = td:tag('div')
:addClass('druid-main-images-labels')
:addClass('druid-tabs')
if isSection then
container:addClass('druid-section-tabs')
end
for i, item in ipairs(tabs) do
local label = container:tag('div')
:addClass('druid-main-images-label')
:addClass('druid-tab')
:addClass('druid-toggleable')
:attr('data-druid', h.counter .. '-' .. i)
:wikitext(texts[i])
:attr('data-druid-tab-key', item)
if isSection then
label:addClass('druid-section-tab')
else
label:addClass('druid-title-tab')
end
end
if args[item .. '_focused'] then
if lib.isNotEmpty(a.icon_bottom_right) then
label:addClass('focused')
node_card:tag('span')
:addClass('card-icon-bottom-right')
:cssText(a.icon_bottom_right_style)
:wikitext(a.icon_bottom_right)
end
end
-- this can be null, don't concat anything here
if lib.isNotEmpty(a.equipped) then
label:addClass(args[item .. '_class'])
node_card:tag('span')
end
:addClass('card-equipped')
end
:wikitext(a.equipped)
 
function h.makeGridSection(section, sectionFields, args, numCols)
local numItems = h.countItems(sectionFields, section, args)
if numItems == 0 then return end
local node = mw.html.create(h.getTag('section'))
:addClass('druid-section-container')
h.printSectionHeader(node, section, args)
h.printSectionTabs(node, section, args)
local tr = node:tag(h.getTag('row'))
:attr('data-druid-section-row', h.escape(section))
if args[section .. '_collapsed'] then
tr:addClass('druid-collapsed')
end
local grid = tr:tag(h.getTag('cell'))
:attr('colspan', 2)
:addClass('druid-grid-section')
:addClass('druid-grid-section-' .. h.escape(section))
:addClass(args[section .. '_class']) -- warning: class can be nil, don't concat anything
:tag('div')
:addClass('druid-grid')
local row, col, i = 1, 1, 1
local sizeOfLastRow = numItems % numCols
local lcm = h.getNumGridCols(numItems, sizeOfLastRow, numCols)
grid:css('grid-template-columns', ('repeat(%s, 1fr)'):format(lcm))
local size = lcm / numCols
for _, item in ipairs(sectionFields) do
local node = mw.html.create('div')
local shouldPrint = h.printData(node, item, section, args)
if shouldPrint then
if i == numItems - sizeOfLastRow + 1 then
size = lcm / sizeOfLastRow
end
i = i + 1
local gStart = (col - 1) * size + 1
local gEnd = (col) * size + 1
local itemContainer = grid:tag('div')
:addClass('druid-grid-item')
:addClass('druid-grid-item-' .. h.escape(item))
:addClass(args[item .. '_class']) -- warning: class can be nil, don't concat anything
:css('grid-column', ('%s / %s'):format(gStart, gEnd))
:css('grid-row', row)
if not h.castBool(args[item .. '_nolabel']) then
h.printLabel(itemContainer:tag('div'), item, args)
end
itemContainer:node(node)
if col == numCols then
row = row + 1
col = 1
else
col = col + 1
end
end
end
end
return node
if lib.isNotEmpty(a.text) then
end
local node_text = node_card:tag('span')
 
:addClass('card-text')
function h.makeSection(section, sectionFields, args)
:addClass('card-font')
if section == '' then return end -- bruteforce fix for trailing commas
:wikitext(a.text)
local shouldPrint = false
local container = mw.html.create(h.getTag('section'))
if a.text_size then
:addClass('druid-section-container')
node_text:addClass('card-text-' .. a.text_size)
:addClass(args[section .. '_class']) -- warning: class can be nil, don't concat anything
h.printSectionHeader(container, section, args)
h.printSectionTabs(container, section, args)
for _, item in ipairs(sectionFields) do
local node = mw.html.create(h.getTag('cell'))
local shouldPrintItem = h.printData(node, item, section, args)
if shouldPrintItem then
shouldPrint = true
local tr = container:tag(h.getTag('row'))
:addClass('druid-row')
:addClass('druid-row-' .. h.escape(item))
:addClass(args[item .. '_class']) -- warning: class can be nil, don't concat anything
:attr('data-druid-section-row', h.escape(section))
if args[section .. '_collapsed'] then
tr:addClass('druid-collapsed')
end
end
if h.castBool(args[item .. '_wide']) or h.castBool(args[item .. '_nolabel']) then
node
if a.multiline_text then
:attr('colspan', 2)
node_text:addClass('multi-line')
:addClass('druid-data-wide')
else
h.printLabel(tr:tag(h.getTag('label')), item, args)
end
end
tr:node(node)
end
end
end
if not shouldPrint then return nil end
if lib.isNotEmpty(a.refinement) then
return container
node_card:tag('span')
end
:addClass('card-refinement')
 
:addClass('refine-' .. a.refinement)
function h.countItems(sectionFields, section, args)
:wikitext(' R', (a.refinement:gsub('a', ''))) -- extra parens to take only first value returned from gsub
local numItems = 0
for _, v in ipairs(sectionFields) do
-- we aren't actually printing here, but we're finding out if we should print anything
-- because we need the count of columns before we print anything in grid data
if h.printData(mw.html.create(), v, section, args) then
numItems = numItems + 1
end
end
end
if lib.isNotEmpty(a.constellation) then
return numItems
node_card:tag('span')
end
:addClass('card-constellation')
 
:addClass('constellation-' .. a.constellation)
function h.getNumGridCols(numItems, sizeOfLastRow, numCols)
:wikitext(' C', a.constellation)
if not numCols then return numItems, 1 end
node_card:addClass('card-with-constellation')
if numItems < numCols then return numItems, 1 end
if sizeOfLastRow == 0 then
return numCols, 1
end
local a, b = sizeOfLastRow, numCols
while b ~= 0 do
a, b = b, a % b
end
local lcm = sizeOfLastRow * numCols / a
return lcm
end
 
function h.printLabel(node, item, args)
return node
:addClass('druid-label')
:addClass('druid-label-' .. h.escape(item))
:wikitext(args[item .. '_display'] or args[item .. '_label'] or item)
end
 
function h.printData(node, item, section, args)
-- prints data to the node
-- and also returns whether the item is nonempty or not
local hasData = false
local sectionTabs = args[section .. '_tabs']
local tabs = args.tabs
if sectionTabs and #sectionTabs > 0 then
tabs = sectionTabs
end
if not tabs or #tabs == 0 then
return h.printSimpleData(node, item, args)
end
if not h.hasComplexData(item, tabs, args) then
return h.printSimpleData(node, item, args)
end
hasData = hasData or h.printTabbedDataItem(node, item, tabs, args)
if hasData then
node:addClass('druid-data')
end
return hasData
end
 
function h.getTabbedContent(args, label, item)
return args[label .. '_' .. item] or args[item] or TABBED_NONEXIST
end
 
function h.printSimpleData(node, item, args)
if args[item] and type(args[item]) ~= 'string' then
error(("Invalid use of field %s as both a section and a data value"):format(item))
end
if not args[item] then return false end
node:addClass('druid-data')
:addClass('druid-data-' .. h.escape(item))
:addClass('druid-data-nonempty')
:wikitext('\n\n' .. args[item])
return true
end
 
function h.hasComplexData(item, tabs, args)
for _, v in ipairs(tabs) do
if args[v .. '_' .. item] then return true end
end
return false
end
 
function h.printSectionHeader(node, section, args)
if h.castBool(args[section .. '_nolabel']) then return end
local tr = node:tag(h.getTag('row'))
:attr('data-druid-section', h.escape(section))
local th = tr:tag(h.getTag('sectionTitle'))
:attr('colspan', 2)
:addClass('druid-section')
:addClass('druid-section-' .. h.escape(section))
if args[section .. '_collapsible'] or args[section .. '_collapsed'] then
tr:addClass('druid-collapsible')
if args[section .. '_collapsed'] then
tr:addClass('druid-collapsible-collapsed')
end
end
end
end
local emptySections = {}
for _, label in ipairs(args.tabs) do
local hasLabel = false
if a.show_caption and lib.isNotEmpty(a.caption) then
for _, item in ipairs(args[section] or {}) do
local node_caption = node_container:tag('span')
if h.getTabbedContent(args, label, item) then
:addClass('card-caption')
hasLabel = true
:wikitext(' ', a.nolink and a.caption or ('[[' .. a.final_link .. '|' .. a.caption .. ']]'))
end
setCaptionWidth(node_caption, a.caption_width)
end
if not hasLabel then emptySections[label] = true end
end
end
if not next(emptySections) then
th:wikitext(args[section .. '_label'] or section)
return
end
for i, label in ipairs(args.tabs) do
local div = th:tag('div')
:addClass('druid-toggleable-heading')
:addClass('druid-toggleable')
:attr('data-druid', h.counter .. '-' .. i)
:wikitext(args[section .. '_label'] or section)
-- we are going to print the section content even in empty nodes
-- for compatibility with browsers without :has, where hiding empty rows won't happen
if emptySections[label] then
div:addClass('druid-toggleable-heading-empty')
end
if args[label .. '_focused'] then
div:addClass('focused')
end
end
end
function h.printSectionTabs(node, section, args)
local tabs = args[section .. '_tabs']
if not tabs or #tabs == 0 then return end
local tr = node:tag(h.getTag('sectionTabsOuter'))
:attr('data-druid-section', h.escape(section))
:attr('data-druid-section-tabs', h.escape(section))
local th = tr:tag(h.getTag('sectionTabs'))
:attr('colspan', 2)
:addClass('druid-section-tabs')
:addClass('druid-section-tabs-' .. h.escape(section))
local texts = {}
for i, item in ipairs(tabs) do
texts[i] = args[item .. '_label'] or item
end
h.printTabs(th, tabs, texts, true, args)
end
----------------------------
-- general utility functions
----------------------------
function h.overwrite()
-- this is a generic utility function that collects args from the invoke call & the parent template.
-- normally, you merge args with parent template overwriting the invoke call, but
-- since we'll be putting markup/formatting into our invoke call,
-- we actually want to overwrite what the user sent.
local f = mw.getCurrentFrame()
local origArgs = f.args
local parentArgs = f:getParent().args
local args = {}
for k, v in pairs(parentArgs) do
if lib.isNotEmpty(a.note) then
v = mw.text.trim(v)
local node_note = node_container:tag('span')
if v ~= '' then
:addClass('card-caption')
args[k] = v
:wikitext(a.note)
end
setCaptionWidth(node_note, a.caption_width)
end
end
for k, v in pairs(origArgs) do
if a.mobile_list then
v = mw.text.trim(tostring(v))
if a.text ~= '&mdash;' or a.caption or a.note then
if v ~= '' then
local node_mobile_text = node_card
args[k] = v
: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
end
return args
if a.uses_deprecated_params then
end
node_container:wikitext('[[Category:Pages Using Deprecated Template Parameters]]')
 
-- generic utility functions
-- these would normally be provided by other modules, but to make installation easy
-- I'm including everything here
 
function h.split(text, pattern, plain)
if not text then
return {}
end
local ret = {}
for m in h.gsplit(text, pattern, plain) do
ret[#ret+1] = m
end
end
return ret
end


function h.gsplit( text, pattern, plain )
return node_container
if not pattern then pattern = ',' end
if not plain then
pattern = '%s*' .. pattern .. '%s*'
end
local s, l = 1, text:len()
return function ()
if s then
local e, n = text:find( pattern, s, plain )
local ret
if not e then
ret = text:sub( s )
s = nil
elseif n < e then
-- Empty separator!
ret = text:sub( s, e )
if e < l then
s = e + 1
else
s = nil
end
else
ret = e > s and text:sub( s, e - 1 ) or ''
s = n + 1
end
return ret
end
end, nil, nil
end
end


-- char-set of characters to escape/replace
function p.templateData(frame)
local escapePattern = '[' -- required for pattern; do not remove.
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.",
.. '"'
format='inline'
.. "'"
}, frame)
.. "%?"
.. "%%"
.. "%["
.. "%]"
.. "{"
.. ']' -- required for pattern; do not remove.
 
function h.escape(s)
s = s:gsub(escapePattern, '')
return s
end
end


 
function p.syntax(frame)
-- normally I would make these constants at the top of the file
return TemplateData.syntax(CARD_ARGS, frame)
-- but I don't want to mistake them with user-set constants
h.boolFalse = { ['false'] = true, ['no'] = true, [''] = true, ['0'] = true, ['nil'] = true }
 
function h.castBool(x)
if not x then return false end
return not h.boolFalse[tostring(x):lower()]
end
 
h.htmlEntities = {
table = {
container = 'table',
titleOuter = 'tr',
titleInner = 'th',
section = '',
sectionTitle = 'th',
sectionTabsOuter = 'tr',
sectionTabs = 'td',
row = 'tr',
label = 'th',
cell = 'td',
},
div = {
container = 'div',
titleOuter = 'div',
titleInner = 'div',
section = 'div',
sectionTitle = 'div',
sectionTabsOuter = 'div',
sectionTabs = 'div',
row = 'div',
label = 'div',
cell = 'div',
}
}
 
function h.getTag(key)
-- try not to totally fail here
return h.htmlEntities[h.entityType or 'div'][key]
end
end


return p
return p

Revision as of 13:28, 21 April 2026

Module documentation

This is a testing module for User:Stevium.

local p = {}
local lib = require('Module:Feature')
local TemplateData = require('Module:TemplateData')
local Icon = require('Module:Icon')


local QUALITY_STARS = {
	['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 = {
	{
		name='name', alias={1,'character'}, displayName=1,
		status='suggested',
		label='Name',
		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',
		label='No Link',
		trueDescription='remove default links from card image, icons, and caption.',
		displayDefault='"1" if Image Name is default ("Unknown")',
	},
	{
		name='icon', displayType='content',
		label='Icon',
		description='The icon at the top left of the card.',
		example={'{{Icon/Element|Pyro|25}}', '[[File:Item Primogem.png|25px]]'},
	},
	{
		name='icon_style', displayType='string',
		label='Icon Style',
		description='The custom style of the icon at the top left of the card. Omit the \'\' used for CSS.',
		example='background: red;',
	},
	{
		name='icon_right', displayType='content',
		label='Top-Right Icon',
		description='Icon on the top right of the card.',
		example='{{Icon/Element|Pyro|25}}',
	},
	{
		name='icon_right_style', displayType='string',
		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',
		label='Bottom-Left Icon',
		description='Icon on the bottom left of the card.',
		example='{{Icon/Element|Pyro|25}}',
	},
	{
		name='icon_bottom_left_style', displayType='string',
		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',
		label='Bottom-Right Icon',
		description='Icon on the bottom right of the card.',
		example='{{Icon/Element|Pyro|25}}',
	},
	{
		name='icon_bottom_right_style', displayType='string',
		label='Bottom-Right Icon Style',
		description='The custom style of the icon at the bottom right of the card. Omit the \'\' used for CSS.',
		example='background: red;',
	},
	{
		name='show_caption', type='1',
		label='Show Caption',
		trueDescription='show caption text below the card.',
	},
	{
		name='caption', displayType='string',
		label='Caption Text',
		description='The linked caption text that shows below the card.',
		displayDefault='same as Name',
		defaultFrom='name',
		example={'Apple', 'Rewards'},
	},
	{
		name='caption_width',
		label='Caption Width',
		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',
		label='Caption Note',
		description='The unlinked caption text that shows below the linked caption text.',
		example='(Bonus reward)',
	},
	{
		name='refinement', alias='r', displayType='number',
		label='Refinement Rank',
		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='constellation', alias='c', displayType='number',
		label='Constellation Level',
		description='Constellation Level of the Character.',
		example={'0', '1', '2', '3', '4', '5', '6'},
	},
	{
		name='outfit', placeholderFor='Module:Icon',
	},
	{
		name='stars', type='1',
		label='Show Item Stars',
		trueDescription='show the stars of the quality of the item. (Does not work if quality is "0" or split (e.g., "123" or "34").)',
	},
	{
		name='set', type='1',
		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)
	local args = require('Module:Arguments').getArgs(frame, {
		parentFirst = true,
		wrapper = { 'Template:Card' }
	})
	return p._main(args, frame)
end

-- apply defaults; load and apply data (e.g., quality, character element)
function p._processArgs(args)
	local out = TemplateData.processArgs(args, CARD_ARGS)
	
	-- set defaults that depend on other arguments
	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

	-- handle deprecated caption value
	if tostring(out.caption) == '1' then
		out.uses_deprecated_params = true
		out.caption = nil
		out.show_caption = true
	end
	
	-- handle nolink
	out.final_link = out.nolink and '' or (out.link .. out.link_suffix)
	
	-- build args for main card image
	local mainImageArgs = Icon.extractIconArgs(out)
	mainImageArgs.link = out.final_link
	mainImageArgs.size = 74
	
	-- check for prefixes in `name` that correspond to top-left icons
	-- 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
		mainImageArgs.name = baseName
	end
	
	-- create card image and get type/data
	local card_type, data
	out.image, card_type, data = Icon.createIcon(mainImageArgs)
	
	-- set defaults based on data
	if data then
		if card_type == 'Character' then
			if out.icon_name == out.defaults.icon_name and data.element ~= 'Adaptive' and lib.isNotEmpty(data.element) then
				out.icon_name = data.element
				out.icon_type = 'Element'
				out.icon_link = ''
			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
		if card_type == 'Enemy' then
			if out.icon_name == out.defaults.icon_name and lib.isNotEmpty(data.element) then
				out.icon_name = data.element
				out.icon_type = 'Element'
				out.icon_link = ''
			end
			out.element = data.element or out.element
		end
		if card_type == 'Fish' then
			if out.icon_name == out.defaults.icon_name and lib.isNotEmpty(data.bait) then
				out.icon_name = data.bait
				out.icon_type = 'Item'
				out.icon_link = ''
			end
			out.prefix = ''
		end
		if card_type == 'Food' then
			if out.icon_name == out.defaults.icon_name and lib.isNotEmpty(data.effectType) then
				out.icon_name = data.effectType
				out.icon_type = 'Icon'
				out.icon_link = ''
			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
	
	-- set other defaults based on card type
	if card_type == 'Artifact Set' then
		out.defaults.set = true
	end
	
	-- add top-left icon (if not specified by 'icon' wikitext)
	if out.icon == out.defaults.icon then
		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
	
	-- add top-right icon (if not specified by 'icon_right' wikitext)
	if out.icon_right == out.defaults.icon_right then
		local iconImage = Icon.createIcon(out, 'icon_right_')
		
		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_right = iconImage:buildString()
	end
	
	-- 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
		out.icon_bottom_left = iconImage:buildString()
	end
	
	-- add top-right icon (if not specified by 'icon_bottom_right' wikitext)
	if out.icon_bottom_right == out.defaults.icon_bottom_right then
		local iconImage = Icon.createIcon(out, 'icon_bottom_right_')
		
		iconImage.defaults.size = lib.ternary(out.mini, 10, 20)
		-- disable link if nolink is explicitly set, but icon_link is not
		if out.nonDefaults.nolink then
			iconImage.defaults.link = ''
		end
		out.icon_bottom_right = iconImage:buildString()
	end
	
	-- add equipped icon
	if lib.isNotEmpty(out.equipped_name) then
		local equippedImage, equippedType = Icon.createIcon(out, 'equipped_')
		if equippedType == 'Character' then
			equippedImage.defaults.size = 50
			equippedImage.defaults.suffix = 'Side Icon'
			equippedImage.defaults.alt = 'Equipped on ' .. equippedImage.alt
		else
			equippedImage.defaults.alt = 'Equipped with ' .. equippedImage.alt
		end
		if out.nonDefaults.nolink then
			equippedImage.defaults.link = ''
		end
		out.equipped = equippedImage:buildString()
	end
	
    -- add enemy danger icon
	if out.danger then
        out.icon_right = '[[File:Icon Warning.png|25x25px|link=|alt=Warning icon]]'
        out.icon_right_style = 'top: -8px; right: -10px; z-index: 1'
	end
	
	return out
end

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

function p._main(args, frame)
	local a = p._processArgs(args)
	
	local node_container = mw.html.create('div'):addClass('card-container')
		
	if a.mini then
		node_container:addClass('mini-card')
	end
	
	if a.miliastra_wonderland then
		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')
		:addClass('card-wrapper')
		:tag('span')
			:addClass('card-body')
	
	local node_image = node_card:tag('span')
		:addClass('card-image-container')
	if lib.isNotEmpty(a.quality) then
		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(' ', '%-')))
	end	
	if lib.isNotEmpty(a.weapon) then node_image:addClass('card-weapon-' .. string.lower(a.weapon:gsub(' ', '%-'))) end
	if lib.isNotEmpty(a.element) then node_image:addClass('card-element-' .. string.lower(a.element:gsub(' ', '%-'))) end
	node_image:tag('span')
		:wikitext(a.image:buildString())
	
	if lib.isNotEmpty(a.icon) then
		node_card:tag('span')
			:addClass('card-icon')
			:cssText(a.icon_style)
			:wikitext(a.icon)
	end
		
	if a.mini then
		if lib.isNotEmpty(a.text) and tonumber((a.text:gsub(',', ''):gsub('[kKmMbBtT%%]$', ''))) ~= nil then
			node_card:tag('span'):addClass('card-text'):wikitext(a.text)
		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
	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
	
	
	if a.show_caption and lib.isNotEmpty(a.caption) then
		local node_caption = node_container:tag('span')
			:addClass('card-caption')
			:wikitext(' ', a.nolink and a.caption or ('[[' .. a.final_link .. '|' .. a.caption .. ']]'))
		setCaptionWidth(node_caption, a.caption_width)
	end
	
	if lib.isNotEmpty(a.note) then
		local node_note = node_container:tag('span')
			:addClass('card-caption')
			:wikitext(a.note)
		setCaptionWidth(node_note, a.caption_width)
	end
	
	if a.mobile_list then
		if a.text ~= '&mdash;' or a.caption or a.note then
			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
	
	if a.uses_deprecated_params then
		node_container:wikitext('[[Category:Pages Using Deprecated Template Parameters]]')
	end

	return node_container
end

function p.templateData(frame)
	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.",
		format='inline'
	}, frame)
end

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

return p