Module:Card: Difference between revisions
Appearance
ReisuDesign (talk | contribs) Adds support for split image |
ReisuDesign (talk | contribs) Undo revision 32042 by ReisuDesign (talk) Tag: Undo |
||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
| Line 8: | Line 7: | ||
local allData = { | local allData = { | ||
--Neighbor | |||
{'Neighbor', mw.loadData('Module:CentralData/Neighbors')}, | {'Neighbor', mw.loadData('Module:CentralData/Neighbors')}, | ||
--Creatures | |||
{'Fish', mw.loadData('Module:CentralData/Aquatic Creatures')}, | {'Fish', mw.loadData('Module:CentralData/Aquatic Creatures')}, | ||
{'Insect', mw.loadData('Module:CentralData/Land Creatures')}, | {'Insect', mw.loadData('Module:CentralData/Land Creatures')}, | ||
{'Shore-Dweller', mw.loadData('Module:CentralData/Shore-Dwellers')}, | {'Shore-Dweller', mw.loadData('Module:CentralData/Shore-Dwellers')}, | ||
--Furniture | |||
{'Furniture', mw.loadData('Module:CentralData/Furniture')}, | {'Furniture', mw.loadData('Module:CentralData/Furniture')}, | ||
--Items | |||
{'Item', mw.loadData('Module:CentralData/Items')}, | {'Item', mw.loadData('Module:CentralData/Items')}, | ||
{'Dish', mw.loadData('Module:CentralData/Dishes')}, | {'Dish', mw.loadData('Module:CentralData/Dishes')}, | ||
{'Crop', mw.loadData('Module:CentralData/Crops')}, | {'Crop', mw.loadData('Module:CentralData/Crops')}, | ||
{'Tool', mw.loadData('Module:CentralData/Tools')}, | {'Tool', mw.loadData('Module:CentralData/Tools')}, | ||
--Clothing | |||
{'Clothing', mw.loadData('Module:CentralData/Clothing')}, | {'Clothing', mw.loadData('Module:CentralData/Clothing')}, | ||
{'Styling', mw.loadData('Module:CentralData/Styling')}, | {'Styling', mw.loadData('Module:CentralData/Styling')}, | ||
--Other | |||
{'Title', mw.loadData('Module:CentralData/Titles')}, | {'Title', mw.loadData('Module:CentralData/Titles')}, | ||
{'Reaction', mw.loadData('Module:CentralData/Reactions')}, | {'Reaction', mw.loadData('Module:CentralData/Reactions')}, | ||
{'Set', mw.loadData('Module:CentralData/Sets')}, | {'Set', mw.loadData('Module:CentralData/Sets')}, | ||
--Redirects (Things that don't need their own pages) | |||
{'Redirect', mw.loadData('Module:CentralData/Redirects')}, | {'Redirect', mw.loadData('Module:CentralData/Redirects')}, | ||
} | } | ||
| Line 60: | Line 53: | ||
{ | { | ||
name = 'type', | name = 'type', | ||
}, | }, | ||
{ | { | ||
| Line 125: | Line 115: | ||
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 | ||
}) | }) | ||
| Line 186: | Line 176: | ||
-- Converts "Luca: Myriad Meadows" to "Myriad Meadows" | -- Converts "Luca: Myriad Meadows" to "Myriad Meadows" | ||
-- Only affects the link target, not the displayed card name. | |||
return mw.text.trim(value:gsub('^.-:%s*', '', 1)) | return mw.text.trim(value:gsub('^.-:%s*', '', 1)) | ||
end | end | ||
| Line 195: | Line 186: | ||
node:css('width', width) | node:css('width', width) | ||
end | end | ||
end | end | ||
| Line 215: | Line 193: | ||
-- Basic identity | -- Basic identity | ||
out.name = out.name or 'Unknown' | out.name = out.name or 'Unknown' | ||
-- Use only a manually supplied type for lookup. | -- Use only a manually supplied type for lookup. | ||
local requestedType = out.nonDefaults | -- This allows {{Card|Sockeye Salmon}} to search Creature, Item, etc. | ||
local requestedType = out.nonDefaults and out.nonDefaults.type or nil | |||
out.type, out.data = p.getTypeAndData(out.name, requestedType) | |||
-- Base name for assets. | |||
out.type, out.data = p.getTypeAndData( | -- Removes a trailing disambiguation such as "(Currency)" | ||
-- while keeping the full name for lookup and links. | |||
) | |||
-- | |||
out.image_name = out.name:gsub('%s*%([^()]*%)$', '') | out.image_name = out.name:gsub('%s*%([^()]*%)$', '') | ||
-- Boolean | -- Boolean params | ||
out.mini = normalizeBool(out.mini) | out.mini = normalizeBool(out.mini) | ||
out.first = normalizeBool(out.first) | out.first = normalizeBool(out.first) | ||
| Line 264: | Line 237: | ||
out.defaults.quality = out.data.quality | out.defaults.quality = out.data.quality | ||
-- Safety fallback in case TemplateData does not re-resolve | |||
-- defaults after processArgs. | |||
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 | ||
| Line 281: | Line 256: | ||
out.quality = normalizeQuality(out.quality) | out.quality = normalizeQuality(out.quality) | ||
-- Label handling | -- Label/text handling | ||
if out.show_label then | if out.show_label then | ||
out.label = out.data | out.label = out.data and (out.data.name or out.data.title) or out.name | ||
end | end | ||
| Line 294: | Line 267: | ||
-- 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 303: | Line 276: | ||
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) | ||
-- Safety fallback in case TemplateData does not re-resolve defaults. | |||
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 320: | Line 294: | ||
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 | out.caption = out.data.title or out.data.name or out.name | ||
else | else | ||
out.caption = out.name | out.caption = out.name | ||
| Line 330: | Line 302: | ||
-- Link handling | -- Link handling | ||
if out.link == nil then | if out.link == nil then | ||
out.link = lib.ternary( | out.link = lib.ternary(out.name == 'Unknown', '', out.name) | ||
end | end | ||
| Line 344: | Line 312: | ||
end | end | ||
-- | -- File name | ||
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 = | 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 | local linkPart = out.no_link and '|link=' or ('|link=' .. out.final_link) | ||
out.image_wikitext = table.concat({ | out.image_wikitext = table.concat({ | ||
| Line 366: | Line 328: | ||
out.image_size, | out.image_size, | ||
'px|alt=', | 'px|alt=', | ||
out.data and (out.data.name or out.data.title) | out.data and (out.data.name or out.data.title) or out.name, | ||
linkPart, | linkPart, | ||
']]' | ']]' | ||
| Line 377: | Line 338: | ||
function p._main(args) | function p._main(args) | ||
local a = p._processArgs(args) | local a = p._processArgs(args) | ||
local node_container = mw.html.create('div') | local node_container = mw.html.create('div') | ||
:addClass('card-container') | :addClass('card-container') | ||
if a.mini then | if a.mini then | ||
| Line 392: | Line 346: | ||
end | end | ||
-- | -- Old-style wrapper/body structure | ||
local node_card = node_container:tag('span') | local node_card = node_container:tag('span') | ||
:addClass('card-wrapper') | :addClass('card-wrapper') | ||
| Line 400: | Line 354: | ||
-- 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( | node_card:addClass('card-galaxy-' .. normalizeClassName(a.galaxy)) | ||
end | end | ||
-- Main image | -- Main image | ||
node_card:tag('span') | |||
:addClass('card-image-container') | :addClass('card-image-container') | ||
:addClass('card-quality-' .. a.quality) | :addClass('card-quality-' .. a.quality) | ||
:wikitext(a.image_wikitext) | |||
-- Galaxy icon | -- Galaxy icon | ||
| Line 432: | Line 369: | ||
:wikitext( | :wikitext( | ||
'[[File:' .. | '[[File:' .. | ||
a.galaxy .. | a.galaxy .. | ||
' Icon.png|alt=' .. | ' Icon.png|alt=' .. | ||
a.galaxy .. | a.galaxy .. | ||
| Line 453: | Line 390: | ||
:addClass('card-modifier') | :addClass('card-modifier') | ||
:addClass('card-modifier-first') | :addClass('card-modifier-first') | ||
:wikitext( | :wikitext('[[File:Icon Item First.png|x25px|link=|alt=First]]') | ||
end | end | ||
| Line 463: | Line 398: | ||
:addClass('card-modifier') | :addClass('card-modifier') | ||
:addClass('card-modifier-up') | :addClass('card-modifier-up') | ||
:wikitext( | :wikitext('[[File:Icon Item UP.png|x25px|link=|alt=UP]]') | ||
end | end | ||
-- Label below | -- Label/text below card image | ||
if ne(a.label) then | if ne(a.label) then | ||
node_container:tag('span') | node_container:tag('span') | ||
| Line 475: | Line 408: | ||
end | end | ||
-- Caption | -- Caption below card | ||
if | if 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 493: | Line 416: | ||
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 506: | Line 427: | ||
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, caption | description = 'Creates an item-style card with image, quality, label, caption, modifiers, and optional data-driven icons.', | ||
format = 'inline' | format = 'inline' | ||
}, frame) | }, frame) | ||
Revision as of 09:28, 9 October 2026
| Main | Documentation | Usage |
This Lua module is used primarily on pages in the Main namespace. It can be found on 2,430 pages, or on about 60.63% 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 allData = {
--Neighbor
{'Neighbor', mw.loadData('Module:CentralData/Neighbors')},
--Creatures
{'Fish', mw.loadData('Module:CentralData/Aquatic Creatures')},
{'Insect', mw.loadData('Module:CentralData/Land Creatures')},
{'Shore-Dweller', mw.loadData('Module:CentralData/Shore-Dwellers')},
--Furniture
{'Furniture', mw.loadData('Module:CentralData/Furniture')},
--Items
{'Item', mw.loadData('Module:CentralData/Items')},
{'Dish', mw.loadData('Module:CentralData/Dishes')},
{'Crop', mw.loadData('Module:CentralData/Crops')},
{'Tool', mw.loadData('Module:CentralData/Tools')},
--Clothing
{'Clothing', mw.loadData('Module:CentralData/Clothing')},
{'Styling', mw.loadData('Module:CentralData/Styling')},
--Other
{'Title', mw.loadData('Module:CentralData/Titles')},
{'Reaction', mw.loadData('Module:CentralData/Reactions')},
{'Set', mw.loadData('Module:CentralData/Sets')},
--Redirects (Things that don't need their own pages)
{'Redirect', mw.loadData('Module:CentralData/Redirects')},
}
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 = 'Gray',
},
{
name = 'type',
},
{
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 = 'galaxy',
},
{
name = 'notice',
},
}
function p.main(frame)
local args = require('Module:Arguments').getArgs(frame, {
wrapper = { 'Template:Card' },
parentOnly = false
})
return p._main(args)
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 = 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 = 'Gray'
end
return normalizeClassName(quality)
end
local function stripLinkPrefix(value)
value = tostring(value or '')
-- Converts "Luca: Myriad Meadows" to "Myriad Meadows"
-- Only affects the link target, not the displayed card name.
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
function p._processArgs(args)
local out = TemplateData.processArgs(args, CARD_ARGS)
-- Basic identity
out.name = out.name or 'Unknown'
-- Use only a manually supplied type for lookup.
-- This allows {{Card|Sockeye Salmon}} to search Creature, Item, etc.
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)"
-- while keeping the full name for lookup and links.
out.image_name = out.name:gsub('%s*%([^()]*%)$', '')
-- Boolean params
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
-- Safety fallback in case TemplateData does not re-resolve
-- defaults after processArgs.
if not (out.nonDefaults and out.nonDefaults.quality) then
out.quality = out.data.quality
end
end
if out.data.galaxy then
out.defaults.galaxy = out.data.galaxy
if not (out.nonDefaults and out.nonDefaults.galaxy) then
out.galaxy = out.data.galaxy
end
end
end
-- Quality / rarity
out.quality = normalizeQuality(out.quality)
-- Label/text 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)
-- Safety fallback in case TemplateData does not re-resolve defaults.
if not (out.nonDefaults and out.nonDefaults.link) then
out.link = stripLinkPrefix(out.name)
end
end
-- Caption handling
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
-- File name
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 node_container = mw.html.create('div')
:addClass('card-container')
if a.mini then
node_container:addClass('mini-card')
end
-- Old-style wrapper/body structure
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
node_card:tag('span')
:addClass('card-image-container')
:addClass('card-quality-' .. a.quality)
:wikitext(a.image_wikitext)
-- Galaxy icon
if not a.mini and ne(a.galaxy) then
node_card:tag('span')
:addClass('card-icon-galaxy')
:wikitext(
'[[File:' ..
a.galaxy ..
' Icon.png|alt=' ..
a.galaxy ..
'|link=' ..
a.galaxy ..
'|20px]]'
)
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/text below card image
if ne(a.label) then
node_container:tag('span')
:addClass('card-label')
:wikitext(a.label)
end
-- Caption below card
if 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, caption, modifiers, and optional data-driven icons.',
format = 'inline'
}, frame)
end
function p.syntax(frame)
return TemplateData.syntax(CARD_ARGS, frame)
end
return p