Jump to content

Module:Card: Difference between revisions

From The Petit Planet Wiki
ReisuDesign (talk | contribs)
Undo revision 32042 by ReisuDesign (talk)
Tag: Undo
No edit summary
 
(10 intermediate revisions by 2 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
--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 = {
local CARD_ARGS = {
Line 49: Line 28:
name = 'quality',
name = 'quality',
alias = 'rarity',
alias = 'rarity',
default = 'Gray',
default = 'Common',
},
},
{
{
name = 'type',
name = 'type',
},
{
name = 'split',
},
},
{
{
Line 104: Line 86:
alias = 'UP',
alias = 'UP',
type = '1',
type = '1',
},
{
name = 'galaxy',
},
},
{
{
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[name]
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 = 'Gray'
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".
-- 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 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.
-- This allows {{Card|Sockeye Salmon}} to search Creature, Item, etc.
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
)
 
-- Base name for assets.
-- Base name for assets.
-- Removes a trailing disambiguation such as "(Currency)"
-- 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 params
-- Boolean parameters
out.mini = normalizeBool(out.mini)
out.mini = normalizeBool(out.mini)
out.first = normalizeBool(out.first)
out.first = normalizeBool(out.first)
Line 237: Line 263:
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
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
end
Line 256: Line 272:
out.quality = normalizeQuality(out.quality)
out.quality = normalizeQuality(out.quality)


-- Label/text handling
-- 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 267: 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 276: 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)
 
-- 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 291: Line 308:
end
end


-- Caption handling
-- 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 302: 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 312: Line 335:
end
end


-- File name
-- 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.image_name) .. out.suffix .. '.' .. out.image_ext
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 328: Line 357:
out.image_size,
out.image_size,
'px|alt=',
'px|alt=',
out.data and (out.data.name or out.data.title) or out.name,
out.data and (out.data.name or out.data.title)
or out.name,
linkPart,
linkPart,
']]'
']]'
Line 338: 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 346: Line 382:
end
end


-- Old-style wrapper/body structure
-- Wrapper and body
local node_card = node_container:tag('span')
local node_card = node_container:tag('span')
:addClass('card-wrapper')
:addClass('card-wrapper')
Line 354: 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)
:wikitext(a.image_wikitext)


-- Galaxy icon
-- Split or regular image
if not a.mini and ne(a.galaxy) then
if isSplit then
node_card:tag('span')
imageContainer:addClass('card-split-image')
:addClass('card-icon-galaxy')
 
:wikitext(
-- First item: top-left half
'[[File:' ..
imageContainer:tag('span')
a.galaxy ..
:addClass('card-split-half')
' Icon.png|alt=' ..
:addClass('card-split-half-a')
a.galaxy ..
:wikitext(a.image_wikitext)
'|link=' ..
 
a.galaxy ..
-- Second item: bottom-right half
'|20px]]'
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 390: 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 398: 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/text below card image
-- Label below the image
if ne(a.label) then
if ne(a.label) then
node_container:tag('span')
node_container:tag('span')
Line 408: Line 453:
end
end


-- Caption below card
-- Caption
if a.show_caption and ne(a.caption) then
-- Split captions are opt-in and use spans instead of list items.
if a.show_caption and isSplit then
local node_caption = node_container:tag('span')
: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 416: Line 480:
node_caption:wikitext(a.caption)
node_caption:wikitext(a.caption)
else
else
node_caption:wikitext('[[', a.final_link, '|', a.caption, ']]')
node_caption:wikitext(
'[[' .. a.final_link .. '|' .. a.caption .. ']]'
)
end
end


Line 427: 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, caption, modifiers, and optional data-driven icons.',
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

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

This module implements {{Card}}.

To update, see: Module:CentralData.

local p = {}

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

local allData = CentralData.dataSheets

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

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

	return p._main(args)
end

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

	if data then
		return data
	end

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

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

	return nil
end

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

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

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

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

	return img_type, img_data
end

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

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

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

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

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

	return normalizeClassName(quality)
end

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

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

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

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

	local size = mini and 74 or 56

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	return out
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		setCaptionWidth(node_caption, a.caption_width)

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

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

		setCaptionWidth(node_caption, a.caption_width)
	end

	return tostring(node_container)
end

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

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

return p