--[[
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.
]]--
local TableTools = require('Module:TableTools')
local InputParser = require('Module:Input parser')
-- Object for the overall collection of cards
-- @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 groups table<FeaturedCardsGroup>
local FeaturedCards = {
character = nil,
characterName = nil,
groups = {},
}
-- Object for a group within the collection (e.g. 'Main Deck' or 'Extra Deck')
-- @field title string
-- @field columns table<FeaturedCardsColumn>
local FeaturedCardsGroup = {
title = nil,
columns = {},
}
-- Object for a column within a group e.g. 'Monster Cards' or 'Spell Cards'
-- @field title string
-- @field lists table<FeaturedCardsList>
local FeaturedCardsColumn = {
title = nil,
lists = {}
}
-- Object for a list within a column e.g. 'Normal Monsters' or 'Continuous Spells'
-- @field title string
-- @field cards table<FeaturedCard>
local FeaturedCardsList = {
title = nil,
cards = {}
}
-- Object for a card within a list
-- @field link string Page name of the card
-- @field name 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 FeaturedCard = {
link = nil,
name = nil,
quantity = nil,
isDebut = false,
mentionedOnly = false,
dubOnly = false
}
-- 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 FeaturedCards object
-- @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)
-- Populate it with data from the arguments
fc.character = args.character
fc.characterName = coalesce(args.characterName, args.character)
return fc
end
-- Add a new group to a FeaturedCards instance
-- @param title string
-- @return FeaturedCardsGroup
function FeaturedCards:addGroup(title)
-- Clone the default object and populate it
local group = mw.clone(FeaturedCardsGroup)
group.title = title
-- Add it to the list of groups
table.insert(self.groups, group)
return group
end
-- Add a new column to a FeaturedCardsGroup instance
-- @param title string
-- @return FeaturedCardsColumn
function FeaturedCardsGroup:addColumn(title)
-- Clone the default object and populate it
local column = mw.clone(FeaturedCardsColumn)
column.title = title
-- Add it to the group's list of columns
table.insert(self.columns, column)
return column
end
-- Add a new list to a FeaturedCardsColumn instance
-- @param title string
-- @param listsInput string
function FeaturedCardsColumn:addList(title, listInput)
-- Clone the default object and populate it
local list = mw.clone(FeaturedCardsList)
list.title = title
list:addCards(listInput)
-- Add it to the column's list of lists
table.insert(self.lists, list)
return list
end
-- Convert an unordered (bulleted) list to a table of cards
-- @param listInput string
-- @return table<FeaturedCard>
function FeaturedCardsList:addCards(listInput)
-- Loop through each list item
for _, cardInput in pairs(InputParser.ulToArray(listInput)) do
local card = mw.clone(FeaturedCard)
-- Parse data from the list item
local link = InputParser.parseLink(cardInput)
local lineArgs = InputParser.getLineArgs(cardInput)
card.link = link.link
card.name = link.text
card.isDebut = TableTools.inArray(lineArgs, 'debut')
card.mentionedOnly = TableTools.inArray(lineArgs, 'mentioned')
card.dubOnly = TableTools.inArray(lineArgs, 'dub')
-- If the link is followed by an "x" or a "×" followed by a number,
-- use the number as the quantity
card.quantity = cardInput:match('%]%]%s+[x×](%d+)')
table.insert(self.cards, card)
end
end
-- Get all list items
-- @return table<FeaturedCard>
function FeaturedCards:getAllCards()
local cards = {}
for _, group in pairs(self.groups) do
for _, column in pairs(group.columns) do
for _, list in pairs(column.lists) do
for _, card in pairs(list.cards) do
table.insert(cards, card)
end
end
end
end
return cards
end
-- Check if a group has cards
-- @return bool
function FeaturedCardsGroup:hasCards()
-- Loop through the columns until one with cards is found
for _, column in pairs(self.columns) do
if (column:hasCards()) then
return true
end
end
-- If none are found
return false
end
-- Check if a column has cards
-- @return bool
function FeaturedCardsColumn:hasCards()
-- Loop through the lists until one with cards is found
for _, list in pairs(self.lists) do
if (list:hasCards()) then
return true
end
end
-- If none are found
return false
end
-- Check if a list has cards
-- @return bool
function FeaturedCardsList:hasCards()
return self.cards and #self.cards > 0
end
-- Check if there are multiple non-empty groups in a `FeaturedCards` instance
-- @return bool
function FeaturedCards:hasMultipleGroups()
local count = 0
-- Loop through groups and increase the counter if the group contains cards
for _, group in pairs(self.groups) do
if (group:hasCards()) then
count = count + 1
-- As soon as a second group with cards is found
-- this function can return `true`
if (count > 1) then
return true
end
end
end
return false
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')
container:css('margin-bottom', '1em')
-- 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
-- The main collapsible section
local bodyHtml = container:tag('div')
bodyHtml:addClass('featured-cards-body mw-collapsible-content')
-- Groups will have headers and be collapsible, if there is more than one of them
local groupsAreCollapsible = self:hasMultipleGroups()
for _, group in pairs(self.groups) do
bodyHtml:wikitext(group:render(groupsAreCollapsible))
end
return tostring(container)
end
-- Render a loaded instance of a FeaturedCardsGroup
-- @param isCollapsible bool
-- @return string
function FeaturedCardsGroup:render(isCollapsible)
-- If it has no cards, render as empty string
if (not self:hasCards()) then return '' end
local container = mw.html.create('div')
if (isCollapsible) then
container:addClass('mw-collapsible')
local heading = container:tag('div')
heading:css({['text-align'] = 'center', ['margin-top'] = '.25em', ['padding'] = '.1em', ['background-color'] = '#ddf' })
heading:wikitext(self.title)
end
local body = container:tag('div')
body:addClass('featured-cards-lists')
body:css({ ['display'] = 'flex', ['flex-wrap'] = 'wrap', ['width'] = '100%' })
if (isCollapsible) then
body:addClass('mw-collapsible-content')
end
for _, column in pairs(self.columns) do
body:wikitext(column:render())
end
return tostring(container)
end
-- Render a loaded instance of a FeaturedCardsColumn
-- @return string
function FeaturedCardsColumn:render()
-- If the column has no cards, render it as an empty string
if (not self:hasCards()) then return '' end
local container = mw.html.create('div'):addClass('featured-cards-list'):css('flex', 1)
-- Add a heading
container:tag('p'):tag('b'):wikitext(self.title)
-- Add each list
for _, list in pairs(self.lists) do
container:wikitext(list:render())
end
return tostring(container)
end
-- Render a loaded instance of a FeaturedCardsList
-- @return string
function FeaturedCardsList:render()
-- If the list has no cards, render it as an empty string
if (not self:hasCards()) then return '' end
local container = mw.html.create('div')
-- Add title if there is one
if (self.title) then
container:tag('p'):wikitext(self.title)
end
-- Add an unordered list for the cards
local ul = container:tag('ul')
for _, card in pairs(self.cards) do
ul:tag('li'):wikitext(card:render())
end
return tostring(container)
end
-- Render the wikitext for displaying a card in a list
-- @return string
function FeaturedCard:render()
-- Start the line with the linked card name
local lineText = '[[' .. self.link .. '|' .. self.name .. ']]'
-- 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()
local cards = self:getAllCards()
for _, card in pairs(cards) do
mw.smw.subobject({
'Card appearing = ' .. card.link,
'English name = ' .. card.name,
'Owner = ' .. (self.character or ''),
'Owner name = ' .. (self.characterName or ''),
'Quantity = ' .. (card.quantity or ''),
})
end
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 characterLink = InputParser.parseLink(args['character'])
local fc = FeaturedCards:new({
character = characterLink.link,
characterName = characterLink.text,
})
local spellName = args['magic'] and 'Magic' or 'Spell'
local extraDeckName = (args['magic'] or args['fusion_deck']) and 'Fusion Deck' or 'Extra Deck'
local mainDeck = fc:addGroup('[[Main Deck]]')
local monsterColumn = mainDeck:addColumn('[[Monster Card]]s')
local spellColumn = mainDeck:addColumn('[[Spell Card|' .. spellName ..' Card]]')
local trapColumn = mainDeck:addColumn('[[Trap Card]]s')
local otherColumn = mainDeck:addColumn('Other')
monsterColumn:addList(nil, args['monsters'] )
monsterColumn:addList('[[Normal Monster]]s', args['normal_monsters'] )
monsterColumn:addList('[[Effect Monster]]s', args['effect_monsters'] )
monsterColumn:addList('[[Flip monster]]s', args['flip_monsters'] )
monsterColumn:addList('[[Toon monster]]s', args['toon_monsters'] )
monsterColumn:addList('[[Spirit monster]]s', args['spirit_monsters'] )
monsterColumn:addList('[[Union monster]]s', args['union_monsters'] )
monsterColumn:addList('[[Gemini monster]]s', args['gemini_monsters'] )
monsterColumn:addList('[[Tuner monster]]s', args['tuner_monsters'] )
monsterColumn:addList('[[Dark Tuner monster]]s', args['dark_tuner_monsters'] )
monsterColumn:addList('[[Ritual Monster]]s', args['ritual_monsters'] )
monsterColumn:addList('[[Pendulum Monster]]s', args['pendulum_monsters'])
monsterColumn:addList('[[Maximum Monster]]s', args['maxmimum_monsters'])
spellColumn:addList(nil, args['spells'] or args['magic'])
spellColumn:addList('[[Normal Spell Card|Normal Spells]]', args['normal_spells'] )
spellColumn:addList('[[Equip Spell Cards|Equip Spells]]', args['equip_spells'] )
spellColumn:addList('[[Field Spell Cards|Field Spells]]', args['field_spells'] )
spellColumn:addList('[[Ritual Spell Cards|Ritual Spells]]', args['ritual_spells'] )
spellColumn:addList('[[Continuous Spell Cards|Continuous Spells]]', args['continuous_spells'])
spellColumn:addList('[[Quick-Play Spell Cards|Quick-Play Spells]]', args['quick_play_spells'])
spellColumn:addList('[[Link Spell Cards|Link Spells]]', args['link_spells'] )
trapColumn:addList(nil, args['traps'] )
trapColumn:addList('[[Normal Trap Card|Normal Traps]]', args['normal_traps'] )
trapColumn:addList('[[Counter Trap Cards|Counter Traps]]', args['counter_traps'] )
trapColumn:addList('[[Equip Trap Cards|Equip Traps]]', args['equip_traps'] )
trapColumn:addList('[[Field Trap Cards|Field Traps]]', args['field_traps'] )
trapColumn:addList('[[Continuous Trap Cards|Continuous Traps]]', args['continuous_traps'])
otherColumn:addList(nil, args['other'] )
otherColumn:addList('[[Equip Card]]s', args['equips'] )
otherColumn:addList('[[Illusion Card]]s', args['illusions'])
otherColumn:addList('[[Virus Card]]s', args['viruses'] )
otherColumn:addList('Unknown', args['unknown'] )
local extraDeck = fc:addGroup('[[Extra Deck|' .. extraDeckName .. ']]')
local fusionColumn = extraDeck:addColumn(nil)
local synchroColumn = extraDeck:addColumn(nil)
local xyzColumn = extraDeck:addColumn(nil)
local linkColumn = extraDeck:addColumn(nil)
fusionColumn:addList( '[[Fusion Monster]]s', args['fusion_monsters'])
synchroColumn:addList('[[Synchro Monster]]s', args['synchro_monsters'])
synchroColumn:addList('[[Dark Synchro Monster]]s', args['dark_synchro_monsters'])
xyzColumn:addList( '[[Xyz Monster]]s', args['xyz_monsters'])
linkColumn:addList( '[[Link Monster]]s', args['link_monsters'])
fc:setSmwData()
return fc:render()
end
return FeaturedCards