--[[
Module for rendering a list of cards featured in something
Typically separate instances of this module are used for each character
This should be used in articles on chapters, episodes, films, etc.
This should not be used for Deck lists on character pages.
]]--
TableTools = require('Module:TableTools')
-- @field character string Page name of the character that the cards belong to
-- @field characterName string Name the character went by in this instance
-- @field lists table Lists of cards grouped by card type
local FeaturedCards = {
character = nil,
characterName = nil,
lists = {}
}
-- @field card string Page name of the card
-- @field cardName string Name the card went by in this instance
-- @field quantity number Amount of copies of the card that appeared
-- @field isDebut boolean If this is the card's first appearance
-- @field mentionedOnly boolean If the card was mentioned, but not seen
-- @field dubOnly boolean The card appears in the English dub, but not the Japanese version
local ListItem = {
card = nil,
cardName = nil,
quantity = nil,
isDebut = false,
mentionedOnly = false,
dubOnly = false
}
local cardTypes = {
monsters = { link = 'Monster Card', text = 'Monster Cards' },
spells = { link = 'Spell Card', text = 'Spell Cards' },
traps = { link = 'Trap Card', text = 'Trap Cards' },
}
-- Convert a wikitext unordered list (bulleted list) to an array (i.e. table)
-- e.g. `'\n* A\n* B\n* C'` -> { 'A', 'B', 'C' }`
--
-- @param text string unordered list in wikitext
-- @return table
local function ulToArray(text)
-- No text, return empty table
if (not text) then return {} end
-- remove initial `*` and spacing
local trimmedText = mw.text.trim(text, '* \n')
-- If the text is empty or only contained whitespace, return empty table
if (trimmedText == '') then return {} end
-- Split by `*`, surrounded in optional spaces
return mw.text.split(trimmedText, '%s*\*%s*')
end
-- Parse data from a wikitext link
-- e.g. `'[[Metal Fiend Token (manga)|Metal Devil]]'` -> `{ link = 'Metal Fiend Token (manga)', text = 'Metal Devil' }`
-- @param linkInput string
-- @return table
local function parseLink(linkInput)
--[[
Pattern for a link in wikitext
- any amount of characters (text before)
- two opering square brackets
- any number of non-pipe characters <-- first capture group
- optional pipe character
- optional amount of any characters <-- second capture group
- two closing square brackets
- any amount of characters (text after)
]]--
local pattern = '.*%[%[([^|]*)|?(.*)%]%].*'
-- Use the first capture group a the link
local link = linkInput:gsub(pattern, '%1')
-- Use the second capture group (characterss after the pipe) as the text
-- If empty, default to the same as the link text
local text = linkInput:gsub(pattern, '%2')
text = (text ~= '') and text or link
return { link = link, text = text }
end
-- Get arguments from a line
-- e.g. `'[[Mystical Moon]] // debut; mentioned; dub; qty::2'`
-- becomes `{ 'debut', 'mentioned', 'dub', ['qty'] = 2 }
--
-- @param lineText string
-- @return table
local function getLineArgs(lineText)
-- Get text after the `//`
local argsText = mw.text.split(lineText, '%s*//%s*')[2] or ''
-- Split by semicolons surrounded by any amount of spaces
-- e.g. { 'debut', 'mentioned', 'dub', 'qty::2' }
local argsArr = mw.text.split(argsText, '%s*;%s*')
local pattern = '(.*)::(.*)'
local args = {}
for _, arg in pairs(argsArr) do
local param, value = arg:match(pattern)
if (not param) then
table.insert(args, arg)
else
args[param] = value
end
end
return args
end
-- Convert an unordered (bulleted) list to a table of cards
-- @param listInput string
-- @return table<ListItem>
local function ulToCardList(listInput)
local listItems = {}
-- Loop through each list item
for _, itemInput in pairs(ulToArray(listInput)) do
local listItem = mw.clone(ListItem)
-- Parse data from the list item
local link = parseLink(itemInput)
local lineArgs = getLineArgs(itemInput)
listItem.card = link.link
listItem.cardName = link.text
listItem.isDebut = TableTools.inArray(lineArgs, 'debut')
listItem.mentionedOnly = TableTools.inArray(lineArgs, 'mentioned')
listItem.dubOnly = TableTools.inArray(lineArgs, 'dub')
listItem.quantity = lineArgs['qty']
table.insert(listItems, listItem)
end
return listItems
end
-- Specify a list of values, get the first non-empty one
-- empty means `nil`, empty string or string containing only whitespace
-- e.g. `coalesce(nil, '', ' ', 'something', nil)` -> `'something'`
-- e.g. `name = coalesce(args.name, pagename)`
local function coalesce(...)
for _, expr in ipairs(arg) do
if (expr and (type(expr) ~= 'string' or mw.text.trim(expr) ~= '')) then
return expr
end
end
return nil
end
-- Create a new instance of a featured cards list
-- @param args table
-- @return FeaturedCards
function FeaturedCards:new(args)
-- Create a new instance of the FeaturedCards object with its default values
local fc = mw.clone(FeaturedCards)
fc.character = args.character
fc.characterName = coalesce(args.characterName, args.character)
return fc
end
-- Render a loaded instance of the object as HTML
-- @return string
function FeaturedCards:render()
-- The element that encapsulates the entire HTML
local container = mw.html.create('div')
container:addClass('featured-cards toccolours mw-collapsible')
-- The heading element, containing the character name
local headerHtml = container:tag('div')
headerHtml:addClass('featured-cards-header')
headerHtml:css({
['background-color'] = '#ccf',
['padding'] = '.2em .3em',
['text-align'] = 'center'
})
if (self.character) then
headerHtml:wikitext('<b>[[' .. self.character .. '|' .. self.characterName .. ']]</b>')
else
-- If `self.character` is empty, don't include a link
headerHtml:wikitext('<b>' .. (self.characterName or 'Cards') .. '</b>')
end
-- Collapsible section containing the card lists
local listsHtml = container:tag('div')
listsHtml:addClass('featured-cards-lists mw-collapsible-content')
listsHtml:css({ ['display'] = 'flex', ['flex-wrap'] = 'wrap', ['width'] = '100%' })
listsHtml:wikitext(self:renderList('monsters'))
listsHtml:wikitext(self:renderList('spells'))
listsHtml:wikitext(self:renderList('traps'))
return tostring(container)
end
-- Render one of the card lists as HTML
-- @param listName string
-- @return string
function FeaturedCards:renderList(listName)
local cards = self.lists[listName]
if (not cards or #cards == 0) then return '' end
local cardType = cardTypes[listName]
local container = mw.html.create('div')
container:addClass('featured-cards-list')
container:css('flex', 1)
container:tag('p'):wikitext('<b>[[' .. cardType.link .. '|' .. cardType.text .. ']]</b>')
local ul = container:tag('ul')
for _, card in pairs(cards) do
local li = ul:tag('li'):wikitext(card:renderLine())
end
return tostring(container)
end
function ListItem:renderLine()
-- Start the line with the linked card name
local lineText = '[[' .. self.card .. '|' .. self.cardName .. ']]'
-- If this is the card's first appearance, italicize it
if (self.isDebut) then
lineText = '<i>' .. lineText .. '</i>'
end
-- If there is more than one, display the quantity next to it
if (self.quantity and tonumber(self.quantity) > 1) then
lineText = lineText .. ' ×' .. self.quantity
end
-- See if there's additional text to display in brackets
local additional = {}
if self.mentionedOnly then table.insert(additional, 'mentioned') end
if self.dubOnly then table.insert(additional, 'dub') end
if (#additional > 0) then
lineText = lineText .. ' (' .. table.concat(additional, ', ') .. ')'
end
return lineText
end
-- @todo Set Semantic MediaWiki data
function FeaturedCards:setSmwData()
end
-- Function that can be called via #invoke
-- Create a FeaturedCards instance from template params
-- And render it as HTML
-- @return string
function FeaturedCards.renderFromTemplate(frame)
local args = frame:getParent().args
local fc = FeaturedCards:new({
character = args['character'],
characterName = args['character_name']
})
fc.lists.monsters = ulToCardList(args.monsters)
fc.lists.spells = ulToCardList(args.spells)
fc.lists.traps = ulToCardList(args.traps)
return fc:render()
end
return FeaturedCards