Module:Card: Difference between revisions
Appearance
No edit summary |
No edit summary |
||
| (15 intermediate revisions by 3 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
| Line 5: | Line 6: | ||
local TemplateData = require('Module:TemplateData') | local TemplateData = require('Module:TemplateData') | ||
local array = require('Module:Array') | local array = require('Module:Array') | ||
local CentralData = require('Module:CentralData') | |||
local allData = | local allData = CentralData.dataSheets | ||
local CARD_ARGS = { | local CARD_ARGS = { | ||
| Line 49: | Line 28: | ||
name = 'quality', | name = 'quality', | ||
alias = 'rarity', | alias = 'rarity', | ||
default = ' | default = 'Common', | ||
}, | }, | ||
{ | { | ||
name = 'type', | name = 'type', | ||
}, | |||
{ | |||
name = 'split', | |||
}, | }, | ||
{ | { | ||
| Line 104: | Line 86: | ||
alias = 'UP', | alias = 'UP', | ||
type = '1', | type = '1', | ||
}, | }, | ||
{ | { | ||
| Line 115: | Line 94: | ||
function p.main(frame) | function p.main(frame) | ||
local args = require('Module:Arguments').getArgs(frame, { | local args = require('Module:Arguments').getArgs(frame, { | ||
wrapper = { 'Template:Card' }, | wrapper = {'Template:Card'}, | ||
parentOnly = false | parentOnly = false | ||
}) | }) | ||
return p._main(args) | 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 | end | ||
| Line 131: | Line 137: | ||
if lib.isEmpty(requestedType) or dataType == requestedType then | if lib.isEmpty(requestedType) or dataType == requestedType then | ||
local data = dataTable | local data = getDataByName(dataType, dataTable, name) | ||
if data then | if data then | ||
| Line 166: | Line 172: | ||
if lib.isEmpty(quality) then | if lib.isEmpty(quality) then | ||
quality = ' | quality = 'Common' | ||
end | end | ||
| Line 175: | Line 181: | ||
value = tostring(value or '') | value = tostring(value or '') | ||
-- Converts "Luca: Myriad Meadows" to "Myriad Meadows" | -- Converts "Luca: Myriad Meadows" to "Myriad Meadows". | ||
return mw.text.trim(value:gsub('^.-:%s*', '', 1)) | return mw.text.trim(value:gsub('^.-:%s*', '', 1)) | ||
end | end | ||
| Line 186: | Line 191: | ||
node:css('width', width) | node:css('width', width) | ||
end | 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 | end | ||
| Line 193: | Line 213: | ||
-- Basic identity | -- Basic identity | ||
out.name = out.name or 'Unknown' | out.name = out.name or 'Unknown' | ||
out.split = out.split or '' | |||
-- Use only a manually supplied type for lookup. | -- Use only a manually supplied type for lookup. | ||
local requestedType = out.nonDefaults | |||
local requestedType = out.nonDefaults and out.nonDefaults.type or nil | and out.nonDefaults.type | ||
out.type, out.data = p.getTypeAndData(out.name, requestedType) | or nil | ||
out.type, out.data = p.getTypeAndData( | |||
out.name, | |||
requestedType | |||
) | |||
-- Boolean | -- 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.mini = normalizeBool(out.mini) | ||
out.first = normalizeBool(out.first) | out.first = normalizeBool(out.first) | ||
| Line 233: | Line 263: | ||
out.defaults.quality = out.data.quality | out.defaults.quality = out.data.quality | ||
if not (out.nonDefaults and out.nonDefaults.quality) then | if not (out.nonDefaults and out.nonDefaults.quality) then | ||
out.quality = out.data.quality | out.quality = out.data.quality | ||
end | end | ||
end | end | ||
| Line 252: | Line 272: | ||
out.quality = normalizeQuality(out.quality) | out.quality = normalizeQuality(out.quality) | ||
-- Label | -- Label handling | ||
if out.show_label then | if out.show_label then | ||
out.label = out.data and (out.data.name or out.data.title) or out.name | out.label = out.data | ||
and (out.data.name or out.data.title) | |||
or out.name | |||
end | end | ||
| Line 263: | Line 285: | ||
-- Type-specific behavior | -- Type-specific behavior | ||
local lcType = tostring(out.type or ''):lower() | local lcType = tostring(out.type or ''):lower() | ||
if lcType == 'neighbor' then | if lcType == 'neighbor' then | ||
if lib.isEmpty(out.label) then | if lib.isEmpty(out.label) then | ||
| Line 272: | Line 294: | ||
end | end | ||
end | end | ||
if tonumber(out.image_size) then | if tonumber(out.image_size) then | ||
out.image_size = tonumber(out.image_size) + 2 | out.image_size = tonumber(out.image_size) + 2 | ||
end | end | ||
end | end | ||
if lcType == 'redirect' then | if lcType == 'redirect' then | ||
out.defaults.link = stripLinkPrefix(out.name) | out.defaults.link = stripLinkPrefix(out.name) | ||
if not (out.nonDefaults and out.nonDefaults.link) then | if not (out.nonDefaults and out.nonDefaults.link) then | ||
out.link = stripLinkPrefix(out.name) | out.link = stripLinkPrefix(out.name) | ||
| Line 287: | Line 308: | ||
end | end | ||
-- Caption | -- Caption defaults | ||
if out.show_caption and lib.isEmpty(out.caption) then | if out.show_caption and lib.isEmpty(out.caption) then | ||
if out.data then | if out.data then | ||
out.caption = out.data.title or out.data.name or out.name | out.caption = out.data.title | ||
or out.data.name | |||
or out.name | |||
else | else | ||
out.caption = out.name | out.caption = out.name | ||
| Line 298: | Line 321: | ||
-- Link handling | -- Link handling | ||
if out.link == nil then | if out.link == nil then | ||
out.link = lib.ternary(out.name == 'Unknown', '', out.name) | out.link = lib.ternary( | ||
out.name == 'Unknown', | |||
'', | |||
out.name | |||
) | |||
end | end | ||
| Line 308: | Line 335: | ||
end | end | ||
-- | -- Main image filename | ||
if ne(out.image) then | if ne(out.image) then | ||
out.final_file = out.image | out.final_file = out.image | ||
else | else | ||
out.final_file = lib.sanitizeFileName(out. | out.final_file = | ||
lib.sanitizeFileName(out.image_name) .. | |||
out.suffix .. | |||
'.' .. | |||
out.image_ext | |||
end | end | ||
-- Main image wikitext | -- Main image wikitext | ||
local linkPart = out.no_link and '|link=' or ('|link=' .. out.final_link) | local linkPart = out.no_link | ||
and '|link=' | |||
or ('|link=' .. out.final_link) | |||
out.image_wikitext = table.concat({ | out.image_wikitext = table.concat({ | ||
| Line 324: | Line 357: | ||
out.image_size, | out.image_size, | ||
'px|alt=', | 'px|alt=', | ||
out.name, | out.data and (out.data.name or out.data.title) | ||
or out.name, | |||
linkPart, | linkPart, | ||
']]' | ']]' | ||
| Line 334: | Line 368: | ||
function p._main(args) | function p._main(args) | ||
local a = p._processArgs(args) | local a = p._processArgs(args) | ||
local isSplit = ne(a.split) | |||
-- Outer container | |||
local node_container = mw.html.create('div') | local node_container = mw.html.create('div') | ||
:addClass('card-container') | :addClass('card-container') | ||
if isSplit then | |||
node_container:addClass('card-split-card') | |||
end | |||
if a.mini then | if a.mini then | ||
| Line 342: | Line 382: | ||
end | end | ||
-- | -- Wrapper and body | ||
local node_card = node_container:tag('span') | local node_card = node_container:tag('span') | ||
:addClass('card-wrapper') | :addClass('card-wrapper') | ||
| Line 350: | Line 390: | ||
-- Galaxy class on body | -- Galaxy class on body | ||
if not a.mini and ne(a.galaxy) then | if not a.mini and ne(a.galaxy) then | ||
node_card:addClass('card-galaxy-' .. normalizeClassName(a.galaxy)) | node_card:addClass( | ||
'card-galaxy-' .. normalizeClassName(a.galaxy) | |||
) | |||
end | end | ||
-- Main image | -- Main image container | ||
node_card:tag('span') | local imageContainer = node_card:tag('span') | ||
:addClass('card-image-container') | :addClass('card-image-container') | ||
:addClass('card-quality-' .. a.quality) | :addClass('card-quality-' .. a.quality) | ||
-- | -- Split or regular image | ||
if | if isSplit then | ||
imageContainer:addClass('card-split-image') | |||
:addClass('card- | |||
: | -- 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 | end | ||
| Line 386: | Line 431: | ||
:addClass('card-modifier') | :addClass('card-modifier') | ||
:addClass('card-modifier-first') | :addClass('card-modifier-first') | ||
:wikitext('[[File:Icon Item First.png|x25px|link=|alt=First]]') | :wikitext( | ||
'[[File:Icon Item First.png|x25px|link=|alt=First]]' | |||
) | |||
end | end | ||
| Line 394: | Line 441: | ||
:addClass('card-modifier') | :addClass('card-modifier') | ||
:addClass('card-modifier-up') | :addClass('card-modifier-up') | ||
:wikitext('[[File:Icon Item UP.png|x25px|link=|alt=UP]]') | :wikitext( | ||
'[[File:Icon Item UP.png|x25px|link=|alt=UP]]' | |||
) | |||
end | end | ||
-- Label | -- Label below the image | ||
if ne(a.label) then | if ne(a.label) then | ||
node_container:tag('span') | node_container:tag('span') | ||
| Line 404: | Line 453: | ||
end | end | ||
-- Caption | -- 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') | local node_caption = node_container:tag('span') | ||
:addClass('card-caption') | :addClass('card-caption') | ||
| Line 412: | Line 480: | ||
node_caption:wikitext(a.caption) | node_caption:wikitext(a.caption) | ||
else | else | ||
node_caption:wikitext('[[' | node_caption:wikitext( | ||
'[[' .. a.final_link .. '|' .. a.caption .. ']]' | |||
) | |||
end | end | ||
| Line 423: | Line 493: | ||
function p.templateData(frame) | function p.templateData(frame) | ||
return TemplateData.templateData(CARD_ARGS, { | return TemplateData.templateData(CARD_ARGS, { | ||
description = 'Creates an item-style card with image, quality, label, | description = 'Creates an item-style card with image, quality, label, split images, modifiers, and optional data-driven icons.', | ||
format = 'inline' | format = 'inline' | ||
}, frame) | }, frame) | ||
Latest revision as of 10:53, 22 October 2026
| Main | Documentation | Usage |
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