Difference between pages "Cross Attack" and "Module:Limitation status list"

From Yugipedia
(Difference between pages)
Jump to: navigation, search
(Updating/sorting {{Search categories}} parameters at {{CardTable2}}. Upcasing attribute.)
 
(`-` needs to be escaped in patterns)
 
Line 1: Line 1:
{{Unofficial name|Croatian}}
+
-- Shell object for the list itself
 +
local List = {
 +
name = nil,
 +
medium = nil,
 +
locality = nil,
 +
startDate = nil,
 +
prev = nil,
 +
next = nil,
 +
items = {},
  
{{CardTable2
+
-- If there is a column for non-English names
| hr_name              = Križani Napad
+
-- The text to display in the column heading
| de_name              = Kreuzangriff
+
localColumnHeading = nil,
| fr_name              = Attaque Croisée
+
-- The name of the SMW property to find the names in
| it_name              = Attacco Incrociato
+
localColumnProperty = nil,
| ko_name              = 크로스 어택
+
 
| pt_name              = Ataque Cruzado
+
errors = {}
| es_name              = Ataque Cruzado
+
}
| ja_name              = クロス・アタック
+
 
| romaji_name          = Kurosu Atakku
+
-- Shell object for an item in the list
| image                = CrossAttack-SP14-EN-C-1E.png
+
local ListItem = {
| attribute            = Spell
+
-- Link to card page. `nil` if not a specific card
| typest                = Normal
+
card = nil,
| number                = 46961802
+
-- Name of the card page, if applicable
| lore                  = [[Target]] 2 [[face-up]] [[Attack Position]] [[Monster Card|monster]]s you [[control]] with the same [[ATK]]; this [[turn]], one of them can [[attack]] your opponent [[Direct attack|directly]] and the other cannot attack.
+
cardName = nil,
| it_lore              = [[Target|Scegli come bersaglio]] 2 mostri scoperti in Posizione di Attacco che [[control]]li con lo stesso ATK; in questo turno, uno di essi può attaccare il tuo avversario [[Direct attack|direttamente]], e l'altro non può attaccare.
+
localCardName = nil,
| ja_lore              = 自分フィールド上に表側攻撃表示で存在する、同じ攻撃力を持つモンスター2体を選択して発動する。このターン、選択したモンスター1体は相手プレイヤーに直接攻撃する事ができる。もう1体のモンスターは攻撃する事ができない。
+
-- Final text to display in the cell
| en_sets              =  
+
displayText = nil,
{{Card table set/header|en}}
+
cardType = nil,
{{Card table set|PHSW-EN048|Photon Shockwave|Common}}
+
status = nil,
{{Card table set|SP14-EN032|Star Pack 2014|Common}}
+
prevStatus = nil,
{{Card table set|SP14-EN032|Star Pack 2014|Starfoil Rare}}
+
prevStatusNote = nil
{{Card table set/footer}}
+
}
| fr_sets              =  
+
 
{{Card table set/header|fr}}
+
-- Main function that gets invoked
{{Card table set|PHSW-FR048|Photon Shockwave|Common}}
+
-- Create a new list from the user input, then render it
{{Card table set|SP14-FR032|Star Pack 2014|Common}}
+
function List.list(frame)
{{Card table set|SP14-FR032|Star Pack 2014|Starfoil Rare}}
+
-- Get the template parameters
{{Card table set/footer}}
+
local args = frame:getParent().args
| de_sets              =  
+
 
{{Card table set/header|de}}
+
local l = List:new(args)
{{Card table set|PHSW-DE048|Photon Shockwave|Common}}
+
return l:render()
{{Card table set|SP14-DE032|Star Pack 2014|Common}}
+
end
{{Card table set|SP14-DE032|Star Pack 2014|Starfoil Rare}}
+
 
{{Card table set/footer}}
+
-- Create a new instance of a list
| it_sets              =  
+
-- @param args table
{{Card table set/header|it}}
+
-- @return List
{{Card table set|PHSW-IT048|Photon Shockwave|Common}}
+
function List:new(args)
{{Card table set|SP14-IT032|Star Pack 2014|Common}}
+
local l = mw.clone(List)
{{Card table set|SP14-IT032|Star Pack 2014|Starfoil Rare}}
+
 
{{Card table set/footer}}
+
l:setData(args or {})
| pt_sets              =  
+
 
{{Card table set/header|pt}}
+
return l
{{Card table set|SP14-PT032|Star Pack 2014|Common}}
+
end
{{Card table set|SP14-PT032|Star Pack 2014|Starfoil Rare}}
+
 
{{Card table set/footer}}
+
-- Main function for setting data for the list
| sp_sets              =  
+
-- @param args table - data from the user input
{{Card table set/header|sp}}
+
function List:setData(args)
{{Card table set|PHSW-SP048|Photon Shockwave|Common}}
+
self.name = args.name or mw.title.getCurrentTitle().text
{{Card table set|SP14-SP032|Star Pack 2014|Common}}
+
self.medium = args.medium
{{Card table set|SP14-SP032|Star Pack 2014|Starfoil Rare}}
+
self.locality = args.locality
{{Card table set/footer}}
+
self.startDate = args.start_date
| jp_sets              =  
+
self.prev = args.prev
{{Card table set/header|jp}}
+
self.next = args.next
{{Card table set|PHSW-JP048|Photon Shockwave|Common}}
+
self:setLocalisationAttributes()
{{Card table set/footer}}
+
 
| kr_sets              =  
+
self.items = {}
{{Card table set/header|kr}}
+
self:addItems('Forbidden', args.forbidden)
{{Card table set|PHSW-KR048|Photon Shockwave|Common}}
+
self:addItems('Limited', args.limited)
{{Card table set/footer}}
+
self:addItems('Semi-Limited', args.semi_limited)
| attack                =  
+
self:addItems('Unlimited', args.no_longer_on_list)
* Allows direct attacks
+
 
* Prevents your monsters from attacking
+
self:setSmwData()
| misc                  = Female
+
end
| database_id          = 9766
+
 
}}
+
function List:setLocalisationAttributes()
 +
if (
 +
self.locality == 'Simplified Chinese' or
 +
self.locality == 'Traditional Chinese' or
 +
self.locality == 'Japanese' or
 +
self.locality == 'Korean'
 +
) then
 +
self.localColumnHeading = self.locality
 +
self.localColumnProperty = self.locality .. ' name'
 +
 
 +
elseif self.medium == 'OCG' or self.medium == 'Yu-Gi-Oh! Official Card Game' then
 +
self.localColumnHeading = 'Japanese'
 +
self.localColumnProperty = 'Japanese name'
 +
 
 +
else
 +
self.localColumnHeading = nil
 +
self.localColumnProperty = nil
 +
end
 +
end
 +
 
 +
-- Add items based on user input
 +
-- @param status string
 +
-- @param listInput string
 +
function List:addItems(status, listInput)
 +
-- Exit early if there is no input
 +
if not isFilled(listInput) then return end
 +
 
 +
-- Split the input by new line and loop through each line
 +
local listItems = mw.text.split(listInput, '\n')
 +
 
 +
for _, line in pairs(listItems) do
 +
-- Split the line input by `//` and get data from each piece
 +
local lineData = mw.text.split(line, '%s*//%s*')
 +
local itemText = lineData[1]
 +
local options = lineData[2]
 +
 
 +
-- Extract data from content after the `//`
 +
local prevStatus = options and options:match('prev::([^;]*)')
 +
local prevStatusNote = options and options:match('prev%-note::([^;]*)')
 +
 
 +
-- Create a new list item object
 +
local listItem = mw.clone(ListItem)
 +
 
 +
listItem.status = status
 +
listItem.prevStatus = prevStatus
 +
listItem.prevStatusNote = prevStatusNote
 +
listItem.displayText = itemText
 +
 
 +
-- Assume it's a card if there's no manual link syntax
 +
local isCard = not itemText:find('%[%[')
 +
 
 +
-- If it's a card, look up the card's data and fill in more of the attributes
 +
if isCard then
 +
local cardData = self:lookupCardData(itemText)
 +
 
 +
if (not cardData) then
 +
table.insert(self.errors, 'Failed to look up details for card: "' .. itemText .. '"')
 +
end
 +
 
 +
listItem.card          = itemText
 +
listItem.cardName      = cardData and cardData.name      or itemText
 +
listItem.localCardName = cardData and cardData.localName or ''
 +
listItem.cardType      = cardData and cardData.cardType
 +
listItem.displayText  = '"[[' .. listItem.card .. '|' .. listItem.cardName .. ']]"'
 +
end
 +
 
 +
-- Add the card to the list
 +
table.insert(self.items, listItem)
 +
end
 +
end
 +
 
 +
function List:setSmwData()
 +
mw.smw.set({
 +
['Effective date'] = self.startDate,
 +
['Medium']         = self.medium,
 +
['Release']       = self.medium,
 +
['Locality']      = self.locality,
 +
['Page type']     = 'Status list'
 +
})
 +
 
 +
-- Go through each list item to add a subobject
 +
for _, item in pairs(self.items) do
 +
-- If it's not a card, don't store any data
 +
if (not item.card) then return end
 +
 
 +
mw.smw.subobject({
 +
'List contains = ' .. item.card,
 +
'Status        = ' .. item.status,
 +
})
 +
end
 +
end
 +
 
 +
-- Find data on a card by suppyling its name
 +
function List:lookupCardData(pageName)
 +
local queryParams = {
 +
'[[' .. pageName  .. ']]',
 +
'?English name = name',
 +
'?Card type#  = cardType',
 +
}
 +
 
 +
if (self.localColumnProperty) then
 +
table.insert(queryParams, '?' .. self.localColumnProperty .. ' = localName')
 +
end
 +
 
 +
local cardData = mw.smw.ask(queryParams)
 +
 
 +
return cardData and cardData[1] or nil
 +
end
 +
 
 +
-- Filter a list's `cards` attribute by a status
 +
-- @return table
 +
function List:getItemsByStatus(status)
 +
-- New array to contain list items with just the specified status
 +
local filteredItems = {}
 +
 
 +
-- Loop through the list of all cards
 +
for _, item in pairs(self.items) do
 +
-- If the item has the specified status, add it to the new array
 +
if (item.status == status) then
 +
table.insert(filteredItems, item)
 +
end
 +
end
 +
 
 +
return filteredItems
 +
end
 +
 
 +
-- Render the overall output
 +
-- @return string
 +
function List:render()
 +
local output = ''
 +
output = output .. self:renderErrors()
 +
output = output .. self:renderNavigation()
 +
output = output .. self:renderStatusList('Forbidden')
 +
output = output .. self:renderStatusList('Limited')
 +
output = output .. self:renderStatusList('Semi-Limited')
 +
output = output .. self:renderStatusList('Unlimited', 'No longer on list')
 +
output = output .. self:renderCategories()
 +
 
 +
return output
 +
end
 +
 
 +
function List:renderErrors()
 +
-- Exit early with empty string if there are no errors.
 +
if (#self.errors == 0) then return '' end;
 +
 
 +
local output = '<div style="color: #d33;">'
 +
output = output .. '\nThis list contains the following issues:\n'
 +
 
 +
for _, error in pairs(self.errors) do
 +
output = output .. '\n* ' ..  error .. ''
 +
end
 +
 
 +
output = output .. '\n</div>'
 +
 
 +
return output
 +
end
 +
 
 +
-- Render the navigation section
 +
-- @return string
 +
function List:renderNavigation()
 +
-- If prev and next are both empty, don't show a navigation menu
 +
if (not isFilled(self.prev) and not isFilled(self.next)) then
 +
return ''
 +
end
 +
 
 +
local currText = self:pageNameToShortName(self.name)
 +
local prevText = self.prev and ('← [[' .. self.prev .. '|' .. self:pageNameToShortName(self.prev) .. ']]') or '&nbsp;'
 +
local nextText = self.next and ('[[' .. self.next .. '|' .. self:pageNameToShortName(self.next) .. ']] →') or '&nbsp;'
 +
 
 +
local output = '<div class="toccolours" style="clear: both; display: flex; margin-bottom: .5em;">'
 +
output = output .. '<div style="flex: 1; text-align: left;">' .. prevText .. '</div>'
 +
output = output .. '<div style="flex: 1; text-align: center;">' .. currText .. '</div>'
 +
output = output .. '<div style="flex: 1; text-align: right;">' .. nextText .. '</div>'
 +
output = output .. '</div>'
 +
 
 +
return output
 +
end
 +
 
 +
-- Convert a page name to a short name
 +
-- e.g. "March 2020 Lists (Duel Links)" -> "March 2020 Lists"
 +
-- e.g. "OCG April 2014 Lists" -> "April 2014 Lists"
 +
-- @param pageName string
 +
-- @return string
 +
function List:pageNameToShortName(pageName)
 +
-- Strip out disambiguation text
 +
local shortName = mw.text.split(pageName, ' %(')[1]
 +
 
 +
-- Remove mention of the medium
 +
shortName = shortName:gsub(self.medium, '')
 +
 
 +
-- Clear any leading/trailing spaces
 +
return mw.text.trim(shortName)
 +
end
 +
 
 +
function List:renderCategories()
 +
-- Don't render any categories if this is outside of the main namespace
 +
if (isFilled(mw.title.getCurrentTitle().nsText)) then return '' end
 +
 
 +
local output = '[[Category:' .. (self.region or '') .. ' ' .. (self.medium or '') .. ' Forbidden & Limited Lists]]'
 +
 
 +
if (#self.errors > 0) then
 +
output = output .. '[[Category:Pages with validation errors]]'
 +
end
 +
 
 +
return output
 +
end
 +
 
 +
-- Render a section with a list fora given status
 +
-- @param status string
 +
-- @param heading status - The text to appear in the heading, if different than the status
 +
-- @return string
 +
function List:renderStatusList(status, heading)
 +
-- Get all list items for the given status
 +
local items = self:getItemsByStatus(status)
 +
 
 +
-- If there are no cards, return empty string and end early
 +
if (#items == 0) then return '' end
 +
 
 +
local heading = mw.html.create('h2'):wikitext(heading or status)
 +
local list = mw.html.create('table'):addClass('wikitable sortable')
 +
 
 +
local headingRow = list:tag('tr')
 +
headingRow:tag('th'):wikitext('Card')
 +
if self.localColumnHeading then
 +
headingRow:tag('th'):wikitext(self.localColumnHeading)
 +
end
 +
headingRow:tag('th'):wikitext('Card type')
 +
headingRow:tag('th'):wikitext('Status')
 +
headingRow:tag('th'):wikitext('Changes')
 +
 
 +
for _, item in pairs(items) do
 +
local cardTypeLink = item.cardType
 +
and ('[[' .. item.cardType .. '|' .. string.gsub(item.cardType, ' Card', '') .. ']]')
 +
or ''
 +
 
 +
-- Form the CSS class that styles the cells in the status column
 +
local statusClass = 'status-' .. (item.status):lower()
 +
 
 +
local row = list:tag('tr')
 +
row:tag('td'):wikitext(item.displayText)
 +
if self.localColumnHeading then
 +
row:tag('td'):wikitext(item.localCardName)
 +
end
 +
row:tag('td'):wikitext(cardTypeLink)
 +
row:tag('td'):addClass(statusClass):wikitext('[[' .. item.status .. ']]')
 +
row:tag('td'):wikitext(
 +
(item.prevStatus and 'was [[' .. item.prevStatus .. ']]' or '') ..
 +
(item.prevStatusNote or '')
 +
)
 +
end
 +
 
 +
return tostring(heading) .. tostring(list)
 +
end
 +
 
 +
-- Check if something has been filled out
 +
-- i.e. check that it's neither `nil` nor empty string
 +
-- @param value mixed
 +
-- @return bool
 +
function isFilled(value)
 +
return value ~= nil and value ~= ''
 +
end
 +
 
 +
return List

Revision as of 15:38, 5 December 2023

-- Shell object for the list itself
local List = {
	name = nil,
	medium = nil,
	locality = nil,
	startDate = nil,
	prev = nil,
	next = nil,
	items = {},

	-- If there is a column for non-English names
	-- The text to display in the column heading
	localColumnHeading = nil,
	-- The name of the SMW property to find the names in
	localColumnProperty = nil,

	errors = {}
}

-- Shell object for an item in the list
local ListItem = {
	-- Link to card page. `nil` if not a specific card
	card = nil,
	-- Name of the card page, if applicable
	cardName = nil,
	localCardName = nil,
	-- Final text to display in the cell
	displayText = nil,
	cardType = nil,
	status = nil,
	prevStatus = nil,
	prevStatusNote = nil
}

-- Main function that gets invoked
-- Create a new list from the user input, then render it
function List.list(frame)
	-- Get the template parameters
	local args = frame:getParent().args

	local l = List:new(args)
	return l:render()
end

-- Create a new instance of a list
-- @param args table
-- @return List
function List:new(args)
	local l = mw.clone(List)

	l:setData(args or {})

	return l
end

-- Main function for setting data for the list
-- @param args table - data from the user input
function List:setData(args)
	self.name = args.name or mw.title.getCurrentTitle().text
	self.medium = args.medium
	self.locality = args.locality
	self.startDate = args.start_date
	self.prev = args.prev
	self.next = args.next
	self:setLocalisationAttributes()

	self.items = {}
	self:addItems('Forbidden', args.forbidden)
	self:addItems('Limited', args.limited)
	self:addItems('Semi-Limited', args.semi_limited)
	self:addItems('Unlimited', args.no_longer_on_list)

	self:setSmwData()
end

function List:setLocalisationAttributes()
	if (
		self.locality == 'Simplified Chinese' or
		self.locality == 'Traditional Chinese' or
		self.locality == 'Japanese' or
		self.locality == 'Korean'
	) then
		self.localColumnHeading = self.locality
		self.localColumnProperty = self.locality .. ' name'

	elseif self.medium == 'OCG' or self.medium == 'Yu-Gi-Oh! Official Card Game' then
		self.localColumnHeading = 'Japanese'
		self.localColumnProperty = 'Japanese name'

	else
		self.localColumnHeading = nil
		self.localColumnProperty = nil
	end
end

-- Add items based on user input
-- @param status string
-- @param listInput string
function List:addItems(status, listInput)
	-- Exit early if there is no input
	if not isFilled(listInput) then return end

	-- Split the input by new line and loop through each line
	local listItems = mw.text.split(listInput, '\n')

	for _, line in pairs(listItems) do
		-- Split the line input by `//` and get data from each piece
		local lineData = mw.text.split(line, '%s*//%s*')
		local itemText = lineData[1]
		local options = lineData[2]

		-- Extract data from content after the `//`
		local prevStatus = options and options:match('prev::([^;]*)')
		local prevStatusNote = options and options:match('prev%-note::([^;]*)')

		-- Create a new list item object
		local listItem = mw.clone(ListItem)

		listItem.status = status
		listItem.prevStatus = prevStatus
		listItem.prevStatusNote = prevStatusNote
		listItem.displayText = itemText

		-- Assume it's a card if there's no manual link syntax
		local isCard = not itemText:find('%[%[')

		-- If it's a card, look up the card's data and fill in more of the attributes
		if isCard then
			local cardData = self:lookupCardData(itemText)

			if (not cardData) then
				table.insert(self.errors, 'Failed to look up details for card: "' .. itemText .. '"')
			end

			listItem.card          = itemText
			listItem.cardName      = cardData and cardData.name      or itemText
			listItem.localCardName = cardData and cardData.localName or ''
			listItem.cardType      = cardData and cardData.cardType
			listItem.displayText   = '"[[' .. listItem.card .. '|' .. listItem.cardName .. ']]"'
		end

		-- Add the card to the list
		table.insert(self.items, listItem)
	end
end

function List:setSmwData()
	mw.smw.set({
		['Effective date'] = self.startDate,
		['Medium']         = self.medium,
		['Release']        = self.medium,
		['Locality']       = self.locality,
		['Page type']      = 'Status list'
	})

	-- Go through each list item to add a subobject
	for _, item in pairs(self.items) do
		-- If it's not a card, don't store any data
		if (not item.card) then return end

		mw.smw.subobject({
			'List contains = ' .. item.card,
			'Status        = ' .. item.status,
		})
	end
end

-- Find data on a card by suppyling its name
function List:lookupCardData(pageName)
	local queryParams = {
		'[[' .. pageName  .. ']]',
		'?English name = name',
		'?Card type#   = cardType',
	}

	if (self.localColumnProperty) then
		table.insert(queryParams, '?' .. self.localColumnProperty .. ' = localName')
	end

	local cardData = mw.smw.ask(queryParams)

	return cardData and cardData[1] or nil
end

-- Filter a list's `cards` attribute by a status
-- @return table
function List:getItemsByStatus(status)
	-- New array to contain list items with just the specified status
	local filteredItems = {}

	-- Loop through the list of all cards
	for _, item in pairs(self.items) do
		-- If the item has the specified status, add it to the new array
		if (item.status == status) then
			table.insert(filteredItems, item)
		end
	end

	return filteredItems
end

-- Render the overall output
-- @return string
function List:render()
	local output = ''
	output = output .. self:renderErrors()
	output = output .. self:renderNavigation()
	output = output .. self:renderStatusList('Forbidden')
	output = output .. self:renderStatusList('Limited')
	output = output .. self:renderStatusList('Semi-Limited')
	output = output .. self:renderStatusList('Unlimited', 'No longer on list')
	output = output .. self:renderCategories()

	return output
end

function List:renderErrors()
	-- Exit early with empty string if there are no errors.
	if (#self.errors == 0) then return '' end;

	local output = '<div style="color: #d33;">'
	output = output .. '\nThis list contains the following issues:\n'

	for _, error in pairs(self.errors) do
		output = output .. '\n* ' ..  error .. ''
	end

	output = output .. '\n</div>'

	return output
end

-- Render the navigation section
-- @return string
function List:renderNavigation()
	-- If prev and next are both empty, don't show a navigation menu
	if (not isFilled(self.prev) and not isFilled(self.next)) then
		return ''
	end

	local currText = self:pageNameToShortName(self.name)
	local prevText = self.prev and ('← [[' .. self.prev .. '|' .. self:pageNameToShortName(self.prev) .. ']]') or '&nbsp;'
	local nextText = self.next and ('[[' .. self.next .. '|' .. self:pageNameToShortName(self.next) .. ']] →') or '&nbsp;'

	local output = '<div class="toccolours" style="clear: both; display: flex; margin-bottom: .5em;">'
	output = output .. '<div style="flex: 1; text-align: left;">' .. prevText .. '</div>'
	output = output .. '<div style="flex: 1; text-align: center;">' .. currText .. '</div>'
	output = output .. '<div style="flex: 1; text-align: right;">' .. nextText .. '</div>'
	output = output .. '</div>'

	return output
end

-- Convert a page name to a short name
-- e.g. "March 2020 Lists (Duel Links)" -> "March 2020 Lists"
-- e.g. "OCG April 2014 Lists" -> "April 2014 Lists"
-- @param pageName string
-- @return string
function List:pageNameToShortName(pageName)
	-- Strip out disambiguation text
	local shortName = mw.text.split(pageName, ' %(')[1]

	-- Remove mention of the medium
	shortName = shortName:gsub(self.medium, '')

	-- Clear any leading/trailing spaces
	return mw.text.trim(shortName)
end

function List:renderCategories()
	-- Don't render any categories if this is outside of the main namespace
	if (isFilled(mw.title.getCurrentTitle().nsText)) then return '' end

	local output = '[[Category:' .. (self.region or '') .. ' ' .. (self.medium or '') .. ' Forbidden & Limited Lists]]'

	if (#self.errors > 0) then
		output = output .. '[[Category:Pages with validation errors]]'
	end

	return output
end

-- Render a section with a list fora given status
-- @param status string
-- @param heading status - The text to appear in the heading, if different than the status
-- @return string
function List:renderStatusList(status, heading)
	-- Get all list items for the given status
	local items = self:getItemsByStatus(status)

	-- If there are no cards, return empty string and end early
	if (#items == 0) then return '' end

	local heading = mw.html.create('h2'):wikitext(heading or status)
	local list = mw.html.create('table'):addClass('wikitable sortable')

	local headingRow = list:tag('tr')
	headingRow:tag('th'):wikitext('Card')
	if self.localColumnHeading then
		headingRow:tag('th'):wikitext(self.localColumnHeading)
	end
	headingRow:tag('th'):wikitext('Card type')
	headingRow:tag('th'):wikitext('Status')
	headingRow:tag('th'):wikitext('Changes')

	for _, item in pairs(items) do
		local cardTypeLink = item.cardType
			and ('[[' .. item.cardType .. '|' .. string.gsub(item.cardType, ' Card', '') .. ']]')
			or ''

		-- Form the CSS class that styles the cells in the status column
		local statusClass = 'status-' .. (item.status):lower()

		local row = list:tag('tr')
		row:tag('td'):wikitext(item.displayText)
		if self.localColumnHeading then
			row:tag('td'):wikitext(item.localCardName)
		end
		row:tag('td'):wikitext(cardTypeLink)
		row:tag('td'):addClass(statusClass):wikitext('[[' .. item.status .. ']]')
		row:tag('td'):wikitext(
			(item.prevStatus and 'was [[' .. item.prevStatus .. ']]' or '') ..
			(item.prevStatusNote or '')
		)
	end

	return tostring(heading) .. tostring(list)
end

-- Check if something has been filled out
-- i.e. check that it's neither `nil` nor empty string
-- @param value mixed
-- @return bool
function isFilled(value)
	return value ~= nil and value ~= ''
end

return List