Module:Infobox
Documentation for this module may be created at Module:Infobox/doc
local p = {}
--------------------------------------------------------------------------------
-- CONFIGURATION
--------------------------------------------------------------------------------
local SCHEMA_TYPES = {
person = 'https://schema.org/Person',
organization = 'https://schema.org/Organization',
nft = 'https://schema.org/CreativeWork',
concept = 'https://schema.org/DefinedTerm',
event = 'https://schema.org/Event',
exhibition = 'https://schema.org/ExhibitionEvent',
artwork = 'https://schema.org/VisualArtwork',
website = 'https://schema.org/WebSite',
}
-- Multi-chain block explorer support
local CHAINS = {
ethereum = {
explorer = 'https://etherscan.io/address/%s',
name = 'Etherscan',
},
base = {
explorer = 'https://basescan.org/address/%s',
name = 'Basescan',
},
arbitrum = {
explorer = 'https://arbiscan.io/address/%s',
name = 'Arbiscan',
},
optimism = {
explorer = 'https://optimistic.etherscan.io/address/%s',
name = 'OP Etherscan',
},
polygon = {
explorer = 'https://polygonscan.com/address/%s',
name = 'Polygonscan',
},
solana = {
explorer = 'https://solscan.io/account/%s',
name = 'Solscan',
},
zora = {
explorer = 'https://explorer.zora.energy/address/%s',
name = 'Zora Explorer',
},
}
-- Marketplace configurations for compact display
-- Order matters: first in list = first displayed
local MARKETPLACES = {
{
id = 'opensea',
name = 'OpenSea',
abbr = 'OS',
},
{
id = 'blur',
name = 'Blur',
abbr = 'Blur',
},
{
id = 'magiceden',
name = 'Magic Eden',
abbr = 'ME',
altParam = 'magic_eden', -- backward compat
},
{
id = 'scatter',
name = 'Scatter',
abbr = 'Scat',
},
}
--------------------------------------------------------------------------------
-- UTILITY FUNCTIONS
--------------------------------------------------------------------------------
local function trim(s)
if not s then return nil end
s = tostring(s)
return s:match("^%s*(.-)%s*$")
end
local function isEmpty(s)
if s == nil then return true end
if type(s) == 'string' then
return trim(s) == ''
end
return false
end
local function hasValue(s)
return not isEmpty(s)
end
local function getArg(args, name, default)
local val = args[name]
if isEmpty(val) then
return default
end
return trim(val)
end
local function getArgWithFallback(args, names, default)
for _, name in ipairs(names) do
local val = getArg(args, name)
if hasValue(val) then
return val
end
end
return default
end
-- Format blockchain address with appropriate explorer link
local function formatAddress(address, chain, display)
if isEmpty(address) then return nil end
address = trim(address)
chain = chain and trim(chain):lower() or 'ethereum'
local chainConfig = CHAINS[chain]
if not chainConfig then
-- Fallback to ethereum if unknown chain
chainConfig = CHAINS['ethereum']
end
-- Check if it looks like a valid address
local isEthStyle = address:match('^0x[a-fA-F0-9]+$')
local isSolana = chain == 'solana' and address:match('^[1-9A-HJ-NP-Za-km-z]+$')
if isEthStyle or isSolana then
local short = display or (isEthStyle and (address:sub(1, 6) .. '...' .. address:sub(-4)) or (address:sub(1, 4) .. '...' .. address:sub(-4)))
local url = string.format(chainConfig.explorer, address)
return mw.html.create('span')
:addClass('infobox-contract')
:wikitext('[' .. url .. ' ' .. short .. ']')
end
-- Return as-is if not a recognized address format
return mw.html.create('span')
:addClass('infobox-contract')
:wikitext(address)
end
-- Legacy wrapper for backward compatibility
local function formatEthAddress(address, display)
return formatAddress(address, 'ethereum', display)
end
-- Format marketplace links (manual only, compact display)
-- Returns mw.html node for compact marketplace badges
local function formatMarketplaces(args)
local links = {}
for _, mp in ipairs(MARKETPLACES) do
-- Check for parameter (support both magiceden and magic_eden style)
local url = args[mp.id]
if isEmpty(url) and mp.altParam then
url = args[mp.altParam]
end
if hasValue(url) then
url = trim(url)
local link = mw.html.create('span')
:addClass('infobox-mp')
:addClass('infobox-mp-' .. mp.id)
:attr('title', mp.name)
:wikitext('[' .. url .. ' ' .. mp.abbr .. ']')
table.insert(links, tostring(link))
end
end
if #links == 0 then
return nil
end
return mw.html.create('span')
:addClass('infobox-marketplaces')
:wikitext(table.concat(links, ' '))
end
-- Format social media handle with link
local function formatSocial(platform, handle)
if isEmpty(handle) then return nil end
handle = trim(handle)
if platform == 'twitter' or platform == 'x' then
handle = handle:gsub('^@', '')
return '[https://x.com/' .. handle .. ' @' .. handle .. ']'
elseif platform == 'farcaster' then
handle = handle:gsub('^@', '')
return '[https://warpcast.com/' .. handle .. ' @' .. handle .. ']'
elseif platform == 'discord' then
-- If it's an invite link, use it directly
if handle:match('discord%.gg') or handle:match('discord%.com') then
return '[' .. handle .. ' Discord]'
end
return handle
end
return handle
end
--------------------------------------------------------------------------------
-- TRACKING CATEGORIES (disabled)
-- Removed to avoid creating indexed category pages that signal "under construction"
-- to search engines and LLMs. Use manual audits instead.
--------------------------------------------------------------------------------
local function getTrackingCategories(spec)
return ''
end
--------------------------------------------------------------------------------
-- ROW GENERATORS
--------------------------------------------------------------------------------
function p.row(label, data, options)
options = options or {}
if isEmpty(data) then return nil end
local tr = mw.html.create('tr')
tr:tag('th')
:addClass('infobox-label')
:attr('scope', 'row')
:wikitext(label)
local td = tr:tag('td')
:addClass('infobox-data')
if options.dataClass then
td:addClass(options.dataClass)
end
if options.itemprop then
td:attr('itemprop', options.itemprop)
end
-- Handle both string data and mw.html nodes
if type(data) == 'table' and data.allDone then
td:node(data)
else
td:wikitext(tostring(data))
end
return tr
end
function p.dataRow(data, options)
options = options or {}
if isEmpty(data) then return nil end
local tr = mw.html.create('tr')
local td = tr:tag('td')
:attr('colspan', '2')
:addClass('infobox-data-full')
if options.class then
td:addClass(options.class)
end
-- Handle both string data and mw.html nodes
if type(data) == 'table' and data.allDone then
td:node(data)
else
td:wikitext(tostring(data))
end
return tr
end
function p.header(text, options)
options = options or {}
if isEmpty(text) then return nil end
local tr = mw.html.create('tr')
tr:tag('th')
:attr('colspan', '2')
:attr('scope', 'colgroup')
:addClass('infobox-header')
:wikitext(text)
return tr
end
function p.subheader(text, options)
options = options or {}
if isEmpty(text) then return nil end
local tr = mw.html.create('tr')
tr:tag('th')
:attr('colspan', '2')
:attr('scope', 'colgroup')
:addClass('infobox-subheader')
:wikitext(text)
return tr
end
function p.image(image, options)
options = options or {}
if isEmpty(image) then return nil end
local size = options.size or '250px'
local caption = options.caption
local alt = options.alt or ''
local fileLink = '[[File:' .. image .. '|' .. size
if hasValue(alt) then
fileLink = fileLink .. '|alt=' .. alt
end
fileLink = fileLink .. ']]'
local tr = mw.html.create('tr')
tr:tag('td')
:attr('colspan', '2')
:addClass('infobox-image')
:wikitext(fileLink)
local result = { tr }
if hasValue(caption) then
local captionTr = mw.html.create('tr')
captionTr:tag('td')
:attr('colspan', '2')
:addClass('infobox-caption')
:wikitext(caption)
table.insert(result, captionTr)
end
return result
end
function p.title(text, options)
options = options or {}
if isEmpty(text) then return nil end
local tr = mw.html.create('tr')
local th = tr:tag('th')
:attr('colspan', '2')
:addClass('infobox-above')
if options.itemprop then
th:attr('itemprop', options.itemprop)
end
th:wikitext(text)
return tr
end
function p.below(text, options)
options = options or {}
if isEmpty(text) then return nil end
local tr = mw.html.create('tr')
tr:tag('td')
:attr('colspan', '2')
:addClass('infobox-below')
:wikitext(text)
return tr
end
--------------------------------------------------------------------------------
-- MAIN BUILD FUNCTION
--------------------------------------------------------------------------------
function p.build(spec)
if not spec then return '' end
local rows = {}
-- Helper to add row(s) to the list
local function addRow(row)
if row then
if type(row) == 'table' and row[1] then
-- Multiple rows returned (e.g., image + caption)
for _, r in ipairs(row) do
table.insert(rows, r)
end
else
table.insert(rows, row)
end
end
end
addRow(p.title(spec.title, {
itemprop = spec.schemaType and 'name' or nil
}))
addRow(p.image(spec.image, {
size = spec.imageSize or '250px',
caption = spec.imageCaption,
alt = spec.imageAlt,
}))
if spec.rows then
for _, rowSpec in ipairs(spec.rows) do
if rowSpec.header then
addRow(p.header(rowSpec.header))
elseif rowSpec.subheader then
addRow(p.subheader(rowSpec.subheader))
elseif rowSpec.label and rowSpec.data then
addRow(p.row(rowSpec.label, rowSpec.data, {
dataClass = rowSpec.dataClass,
itemprop = rowSpec.itemprop
}))
elseif rowSpec.data then
addRow(p.dataRow(rowSpec.data, { class = rowSpec.class }))
end
end
end
addRow(p.below(spec.below))
-- Build the main table
local tbl = mw.html.create('table')
:addClass('infobox')
if hasValue(spec.boxClass) then
tbl:addClass(spec.boxClass)
end
if spec.schemaType and SCHEMA_TYPES[spec.schemaType] then
tbl:attr('itemscope', '')
tbl:attr('itemtype', SCHEMA_TYPES[spec.schemaType])
end
for _, row in ipairs(rows) do
tbl:node(row)
end
-- Wrap in noexcerpt div
local wrapper = mw.html.create('div')
:addClass('noexcerpt')
:node(tbl)
return tostring(wrapper) .. getTrackingCategories(spec)
end
--------------------------------------------------------------------------------
-- HELPERS
--------------------------------------------------------------------------------
function p.getPageTitle()
return mw.title.getCurrentTitle().text
end
local function getFrameArgs(frame)
local args = {}
local parentArgs = frame:getParent().args
local directArgs = frame.args
for k, v in pairs(directArgs) do args[k] = v end
for k, v in pairs(parentArgs) do args[k] = v end
return args
end
--------------------------------------------------------------------------------
-- GENERIC INFOBOX
--------------------------------------------------------------------------------
function p.main(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'title') or getArg(args, 'name') or p.getPageTitle(),
boxClass = getArg(args, 'boxclass'),
image = getArg(args, 'image'),
imageSize = getArg(args, 'image_size') or '250px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
below = getArg(args, 'below'),
templateName = 'generic',
trackMissingImage = false,
}
for i = 1, 30 do
local header = getArg(args, 'header' .. i)
local subheader = getArg(args, 'subheader' .. i)
local label = getArg(args, 'label' .. i)
local data = getArg(args, 'data' .. i)
if hasValue(header) then
table.insert(spec.rows, { header = header })
elseif hasValue(subheader) then
table.insert(spec.rows, { subheader = subheader })
elseif hasValue(data) then
if hasValue(label) then
table.insert(spec.rows, { label = label, data = data })
else
table.insert(spec.rows, { data = data })
end
end
end
return p.build(spec)
end
--------------------------------------------------------------------------------
-- PERSON
--------------------------------------------------------------------------------
function p.person(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'name') or p.getPageTitle(),
boxClass = 'infobox-person',
image = getArg(args, 'image'),
imageSize = getArg(args, 'image_size') or '220px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
schemaType = 'person',
templateName = 'person',
trackMissingImage = true,
}
-- Pseudonym/alias row (only if different from name)
local alias = getArgWithFallback(args, {'alias', 'pseudonym', 'handle'})
local name = getArg(args, 'name') or p.getPageTitle()
if hasValue(alias) and alias ~= name then
table.insert(spec.rows, { label = 'Also known as', data = alias })
end
if hasValue(args.birth_date) then
table.insert(spec.rows, { label = 'Born', data = args.birth_date, itemprop = 'birthDate' })
end
if hasValue(args.nationality) then
table.insert(spec.rows, { label = 'Nationality', data = args.nationality, itemprop = 'nationality' })
end
if hasValue(args.occupation) then
table.insert(spec.rows, { label = 'Occupation', data = args.occupation, itemprop = 'jobTitle' })
end
if hasValue(args.known_for) then
table.insert(spec.rows, { label = 'Known for', data = args.known_for })
end
if hasValue(args.affiliation) then
table.insert(spec.rows, { label = 'Affiliation', data = args.affiliation, itemprop = 'affiliation' })
end
if hasValue(args.years_active) then
table.insert(spec.rows, { label = 'Years active', data = args.years_active })
end
-- Social links
local socials = {}
if hasValue(args.twitter) or hasValue(args.x) then
table.insert(socials, formatSocial('twitter', args.twitter or args.x))
end
if hasValue(args.farcaster) then
table.insert(socials, formatSocial('farcaster', args.farcaster))
end
if #socials > 0 then
table.insert(spec.rows, { label = 'Social', data = table.concat(socials, '<br />') })
end
if hasValue(args.website) then
table.insert(spec.rows, { label = 'Website', data = args.website, itemprop = 'url' })
end
return p.build(spec)
end
--------------------------------------------------------------------------------
-- ORGANIZATION
--------------------------------------------------------------------------------
function p.organization(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'name') or p.getPageTitle(),
boxClass = 'infobox-organization',
image = getArg(args, 'logo') or getArg(args, 'image'),
imageSize = getArg(args, 'image_size') or '200px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
schemaType = 'organization',
templateName = 'organization',
trackMissingImage = false,
}
if hasValue(args.type) then
table.insert(spec.rows, { label = 'Type', data = args.type })
end
if hasValue(args.founded) then
table.insert(spec.rows, { label = 'Founded', data = args.founded, itemprop = 'foundingDate' })
end
if hasValue(args.founder) then
table.insert(spec.rows, { label = 'Founder', data = args.founder, itemprop = 'founder' })
end
if hasValue(args.headquarters) then
table.insert(spec.rows, { label = 'Headquarters', data = args.headquarters })
end
if hasValue(args.key_people) then
table.insert(spec.rows, { label = 'Key people', data = args.key_people })
end
if hasValue(args.products) then
table.insert(spec.rows, { label = 'Products', data = args.products })
end
if hasValue(args.industry) then
table.insert(spec.rows, { label = 'Industry', data = args.industry })
end
-- Social links
local socials = {}
if hasValue(args.twitter) or hasValue(args.x) then
table.insert(socials, formatSocial('twitter', args.twitter or args.x))
end
if hasValue(args.farcaster) then
table.insert(socials, formatSocial('farcaster', args.farcaster))
end
if hasValue(args.discord) then
table.insert(socials, formatSocial('discord', args.discord))
end
if #socials > 0 then
table.insert(spec.rows, { label = 'Social', data = table.concat(socials, '<br />') })
end
if hasValue(args.website) then
table.insert(spec.rows, { label = 'Website', data = args.website, itemprop = 'url' })
end
return p.build(spec)
end
--------------------------------------------------------------------------------
-- NFT COLLECTION
--------------------------------------------------------------------------------
function p.nft(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'name') or p.getPageTitle(),
boxClass = 'infobox-nft',
image = getArg(args, 'image'),
imageSize = getArg(args, 'image_size') or '220px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
schemaType = 'nft',
templateName = 'nft_collection',
trackMissingImage = true,
}
-- Basic info
if hasValue(args.parent_group) then
table.insert(spec.rows, { label = 'Parent group', data = args.parent_group })
end
if hasValue(args.creator) then
table.insert(spec.rows, { label = 'Creator', data = args.creator, itemprop = 'creator' })
end
if hasValue(args.artist) then
table.insert(spec.rows, { label = 'Artist', data = args.artist, itemprop = 'creator' })
end
if hasValue(args.blockchain) then
table.insert(spec.rows, { label = 'Blockchain', data = args.blockchain })
end
if hasValue(args.token_standard) then
table.insert(spec.rows, { label = 'Token standard', data = args.token_standard })
end
if hasValue(args.supply) then
table.insert(spec.rows, { label = 'Supply', data = args.supply })
end
-- Dates
if hasValue(args.launch_date) then
table.insert(spec.rows, { label = 'Launch date', data = args.launch_date, itemprop = 'datePublished' })
end
if hasValue(args.mint_price) then
table.insert(spec.rows, { label = 'Mint price', data = args.mint_price })
end
-- Contract address with chain-aware formatting
if hasValue(args.contract) then
local chain = args.chain and trim(args.chain):lower() or (args.blockchain and trim(args.blockchain):lower()) or 'ethereum'
local formatted = formatAddress(args.contract, chain)
table.insert(spec.rows, { label = 'Contract', data = tostring(formatted) })
end
-- Marketplace links (compact format)
local marketplaceHtml = formatMarketplaces(args)
if marketplaceHtml then
table.insert(spec.rows, { label = 'Marketplaces', data = tostring(marketplaceHtml) })
end
-- License
if hasValue(args.license) then
table.insert(spec.rows, { label = 'License', data = args.license })
end
if hasValue(args.website) then
table.insert(spec.rows, { label = 'Website', data = args.website, itemprop = 'url' })
end
return p.build(spec)
end
--------------------------------------------------------------------------------
-- CONCEPT
--------------------------------------------------------------------------------
function p.concept(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'name') or p.getPageTitle(),
boxClass = 'infobox-concept',
image = getArg(args, 'image'),
imageSize = getArg(args, 'image_size') or '220px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
schemaType = 'concept',
templateName = 'concept',
trackMissingImage = false,
}
if hasValue(args.type) then
table.insert(spec.rows, { label = 'Type', data = args.type })
end
if hasValue(args.coined_by) then
table.insert(spec.rows, { label = 'Coined by', data = args.coined_by })
end
if hasValue(args.coined_date) then
table.insert(spec.rows, { label = 'Date coined', data = args.coined_date })
end
if hasValue(args.origin) then
table.insert(spec.rows, { label = 'Origin', data = args.origin })
end
if hasValue(args.related) then
table.insert(spec.rows, { label = 'Related concepts', data = args.related })
end
if hasValue(args.field) then
table.insert(spec.rows, { label = 'Field', data = args.field })
end
return p.build(spec)
end
--------------------------------------------------------------------------------
-- EVENT
--------------------------------------------------------------------------------
function p.event(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'name') or p.getPageTitle(),
boxClass = 'infobox-event',
image = getArg(args, 'image'),
imageSize = getArg(args, 'image_size') or '220px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
schemaType = 'event',
templateName = 'event',
trackMissingImage = true,
}
if hasValue(args.type) then
table.insert(spec.rows, { label = 'Type', data = args.type })
end
-- Handle date/time flexibility
if hasValue(args.date) then
table.insert(spec.rows, { label = 'Date', data = args.date, itemprop = 'startDate' })
else
if hasValue(args.start_date) then
table.insert(spec.rows, { label = 'Start', data = args.start_date, itemprop = 'startDate' })
end
if hasValue(args.end_date) then
table.insert(spec.rows, { label = 'End', data = args.end_date, itemprop = 'endDate' })
end
end
if hasValue(args.location) then
table.insert(spec.rows, { label = 'Location', data = args.location, itemprop = 'location' })
end
if hasValue(args.venue) then
table.insert(spec.rows, { label = 'Venue', data = args.venue })
end
if hasValue(args.coordinates) then
table.insert(spec.rows, { label = 'Coordinates', data = args.coordinates })
end
if hasValue(args.organizer) then
table.insert(spec.rows, { label = 'Organizer', data = args.organizer, itemprop = 'organizer' })
end
if hasValue(args.host) then
table.insert(spec.rows, { label = 'Host', data = args.host })
end
if hasValue(args.participants) then
table.insert(spec.rows, { label = 'Participants', data = args.participants })
end
if hasValue(args.attendance) then
table.insert(spec.rows, { label = 'Attendance', data = args.attendance })
end
if hasValue(args.outcome) then
table.insert(spec.rows, { label = 'Outcome', data = args.outcome })
end
if hasValue(args.recording) then
table.insert(spec.rows, { label = 'Recording', data = args.recording })
end
if hasValue(args.website) then
table.insert(spec.rows, { label = 'Website', data = args.website, itemprop = 'url' })
end
return p.build(spec)
end
--------------------------------------------------------------------------------
-- EXHIBITION
--------------------------------------------------------------------------------
function p.exhibition(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'name') or p.getPageTitle(),
boxClass = 'infobox-exhibition',
image = getArg(args, 'image'),
imageSize = getArg(args, 'image_size') or '220px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
schemaType = 'exhibition',
templateName = 'exhibition',
trackMissingImage = true,
}
if hasValue(args.type) then
table.insert(spec.rows, { label = 'Type', data = args.type })
end
if hasValue(args.date) then
table.insert(spec.rows, { label = 'Date', data = args.date })
else
if hasValue(args.opening) then
table.insert(spec.rows, { label = 'Opening', data = args.opening, itemprop = 'startDate' })
end
if hasValue(args.closing) then
table.insert(spec.rows, { label = 'Closing', data = args.closing, itemprop = 'endDate' })
end
end
if hasValue(args.venue) then
table.insert(spec.rows, { label = 'Venue', data = args.venue })
end
if hasValue(args.location) then
table.insert(spec.rows, { label = 'Location', data = args.location, itemprop = 'location' })
end
if hasValue(args.curator) then
table.insert(spec.rows, { label = 'Curator', data = args.curator })
end
if hasValue(args.organizer) then
table.insert(spec.rows, { label = 'Organizer', data = args.organizer, itemprop = 'organizer' })
end
if hasValue(args.artists) then
table.insert(spec.rows, { label = 'Artists', data = args.artists })
end
if hasValue(args.works) then
table.insert(spec.rows, { label = 'Featured works', data = args.works })
end
if hasValue(args.attendance) then
table.insert(spec.rows, { label = 'Attendance', data = args.attendance })
end
if hasValue(args.website) then
table.insert(spec.rows, { label = 'Website', data = args.website, itemprop = 'url' })
end
return p.build(spec)
end
--------------------------------------------------------------------------------
-- ARTWORK
--------------------------------------------------------------------------------
function p.artwork(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'title') or p.getPageTitle(),
boxClass = 'infobox-artwork',
image = getArg(args, 'image'),
imageSize = getArg(args, 'image_size') or '220px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
schemaType = 'artwork',
templateName = 'artwork',
trackMissingImage = true,
}
if hasValue(args.artist) then
table.insert(spec.rows, { label = 'Artist', data = args.artist, itemprop = 'creator' })
end
if hasValue(args.year) then
table.insert(spec.rows, { label = 'Year', data = args.year, itemprop = 'dateCreated' })
end
if hasValue(args.medium) then
table.insert(spec.rows, { label = 'Medium', data = args.medium, itemprop = 'artMedium' })
end
if hasValue(args.dimensions) then
table.insert(spec.rows, { label = 'Dimensions', data = args.dimensions })
end
if hasValue(args.token_id) then
table.insert(spec.rows, { label = 'Token ID', data = args.token_id })
end
if hasValue(args.contract) then
local chain = args.chain and trim(args.chain):lower() or 'ethereum'
local formatted = formatAddress(args.contract, chain)
table.insert(spec.rows, { label = 'Contract', data = tostring(formatted) })
end
if hasValue(args.sale_price) then
table.insert(spec.rows, { label = 'Sale price', data = args.sale_price })
end
if hasValue(args.collection) then
table.insert(spec.rows, { label = 'Collection', data = args.collection })
end
if hasValue(args.location) then
table.insert(spec.rows, { label = 'Location', data = args.location })
end
if hasValue(args.exhibition) then
table.insert(spec.rows, { label = 'Exhibition', data = args.exhibition })
end
if hasValue(args.website) then
table.insert(spec.rows, { label = 'Website', data = args.website, itemprop = 'url' })
end
return p.build(spec)
end
--------------------------------------------------------------------------------
-- WEBSITE
--------------------------------------------------------------------------------
function p.website(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'name') or p.getPageTitle(),
boxClass = 'infobox-website',
image = getArg(args, 'logo') or getArg(args, 'screenshot'),
imageSize = getArg(args, 'image_size') or '200px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
schemaType = 'website',
templateName = 'website',
trackMissingImage = false,
}
if hasValue(args.logo) and hasValue(args.screenshot) then
table.insert(spec.rows, { data = '[[File:' .. args.screenshot .. '|250px]]' })
end
if hasValue(args.type) then
table.insert(spec.rows, { label = 'Type', data = args.type })
end
if hasValue(args.owner) then
table.insert(spec.rows, { label = 'Owner', data = args.owner })
end
if hasValue(args.author) then
table.insert(spec.rows, { label = 'Author', data = args.author, itemprop = 'author' })
end
if hasValue(args.url) then
table.insert(spec.rows, { label = 'URL', data = args.url, itemprop = 'url' })
end
if hasValue(args.launch_date) then
table.insert(spec.rows, { label = 'Launched', data = args.launch_date, itemprop = 'dateCreated' })
end
if hasValue(args.current_status) then
table.insert(spec.rows, { label = 'Status', data = args.current_status })
end
if hasValue(args.language) then
table.insert(spec.rows, { label = 'Language', data = args.language })
end
if hasValue(args.registration) then
table.insert(spec.rows, { label = 'Registration', data = args.registration })
end
if hasValue(args.content_license) then
table.insert(spec.rows, { label = 'License', data = args.content_license })
end
if hasValue(args.archive) then
table.insert(spec.rows, { label = 'Archive', data = args.archive })
end
if hasValue(args.predecessor) then
table.insert(spec.rows, { label = 'Predecessor', data = args.predecessor })
end
if hasValue(args.successor) then
table.insert(spec.rows, { label = 'Successor', data = args.successor })
end
return p.build(spec)
end
--------------------------------------------------------------------------------
-- SUBJECT (generic fallback)
--------------------------------------------------------------------------------
function p.subject(frame)
local args = getFrameArgs(frame)
local spec = {
title = getArg(args, 'name') or p.getPageTitle(),
boxClass = '',
image = getArg(args, 'image'),
imageSize = getArg(args, 'image_size') or '250px',
imageCaption = getArg(args, 'caption'),
imageAlt = getArg(args, 'alt'),
rows = {},
templateName = 'subject',
trackMissingImage = false,
}
if hasValue(args.type) then
table.insert(spec.rows, { label = 'Type', data = args.type })
end
if hasValue(args.date) then
table.insert(spec.rows, { label = 'Date', data = args.date })
end
if hasValue(args.location) then
table.insert(spec.rows, { label = 'Location', data = args.location })
end
if hasValue(args.status) then
table.insert(spec.rows, { label = 'Status', data = args.status })
end
if hasValue(args.related) then
table.insert(spec.rows, { label = 'Related', data = args.related })
end
for i = 1, 10 do
local label = getArg(args, 'label' .. i)
local data = getArg(args, 'data' .. i)
if hasValue(label) and hasValue(data) then
table.insert(spec.rows, { label = label, data = data })
end
end
if hasValue(args.website) then
table.insert(spec.rows, { label = 'Website', data = args.website })
end
return p.build(spec)
end
-- Aliases for backward compatibility
p.nft_collection = p.nft
return p