Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
You must create an account or log in to edit.

Module:InfoboxNeue: Difference between revisions

From Amaranth Legacy, available at amaranth-legacy.community
Content deleted Content added
No edit summary
Tag: Manual revert
No edit summary
Line 4: Line 4:
local methodtable = {}
local methodtable = {}


local libraryUtil = require( 'libraryUtil' )
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
local checkTypeMulti = libraryUtil.checkTypeMulti
local i18n = require( 'Module:i18n' ):new()
local i18n = require('Module:i18n'):new()


metatable.__index = methodtable
metatable.__index = methodtable


metatable.__tostring = function ( self )
metatable.__tostring = function(self)
return tostring( self:renderInfobox() )
return tostring(self:renderInfobox())
end
end


-- http://lua-users.org/wiki/SortedIteration
-- http://lua-users.org/wiki/SortedIteration
local function __genOrderedIndex( t )
local function __genOrderedIndex(t)
local orderedIndex = {}
local orderedIndex = {}
for key in pairs(t) do
for key in pairs(t) do
table.insert( orderedIndex, key )
table.insert(orderedIndex, key)
end
end
table.sort( orderedIndex )
table.sort(orderedIndex)
return orderedIndex
return orderedIndex
end
end


local function orderedNext(t, state)
local function orderedNext(t, state)
-- Equivalent of the next function, but returns the keys in the alphabetic
-- Equivalent of the next function, but returns the keys in the alphabetic
-- order. We use a temporary ordered key table that is stored in the
-- order. We use a temporary ordered key table that is stored in the
-- table being iterated.
-- table being iterated.


local key = nil
local key = nil
--print("orderedNext: state = "..tostring(state) )
--print("orderedNext: state = "..tostring(state) )
if state == nil then
if state == nil then
-- the first time, generate the index
-- the first time, generate the index
t.__orderedIndex = __genOrderedIndex( t )
t.__orderedIndex = __genOrderedIndex(t)
key = t.__orderedIndex[1]
key = t.__orderedIndex[1]
else
else
-- fetch the next value
-- fetch the next value
for i = 1,table.getn(t.__orderedIndex) do
for i = 1, table.getn(t.__orderedIndex) do
if t.__orderedIndex[i] == state then
if t.__orderedIndex[i] == state then
key = t.__orderedIndex[i+1]
key = t.__orderedIndex[i + 1]
end
end
end
end
end
end


if key then
if key then
return key, t[key]
return key, t[key]
end
end


-- no more value to return, cleanup
-- no more value to return, cleanup
t.__orderedIndex = nil
t.__orderedIndex = nil
return
return
end
end


local function orderedPairs(t)
local function orderedPairs(t)
-- Equivalent of the pairs() function on tables. Allows to iterate
-- Equivalent of the pairs() function on tables. Allows to iterate
-- in order
-- in order
return orderedNext, t, nil
return orderedNext, t, nil
end
end


Line 65: Line 65:
--- @param key string The translation key
--- @param key string The translation key
--- @return string If the key was not found, the key is returned
--- @return string If the key was not found, the key is returned
local function t( key )
local function t(key)
return i18n:translate( key )
return i18n:translate(key)
end
end


Line 74: Line 74:
--- For some reason SMW property converts underscore into space
--- For some reason SMW property converts underscore into space
--- mw.uri.encode can't be used on full URL
--- mw.uri.encode can't be used on full URL
local function restoreUnderscore( s )
local function restoreUnderscore(s)
return s:gsub( ' ', '%%5F' )
return s:gsub(' ', '%%5F')
end
end


local function removeprefix( s, p )
local function removeprefix(s, p)
return (s:sub(0, #p) == p) and s:sub(#p+1) or s
return (s:sub(0, #p) == p) and s:sub(#p + 1) or s
end
end


Line 85: Line 85:
--- Helper function to format string to number with separators
--- Helper function to format string to number with separators
--- It is usually use to re-format raw number from SMW into more readable format
--- It is usually use to re-format raw number from SMW into more readable format
local function formatNumber( s )
local function formatNumber(s)
local lang = mw.getContentLanguage()
local lang = mw.getContentLanguage()
if s == nil then
if s == nil then
Line 91: Line 91:
end
end


if type( s ) ~= 'number' then
if type(s) ~= 'number' then
s = tonumber( s )
s = tonumber(s)
end
end


if type( s ) == 'number' then
if type(s) == 'number' then
return lang:formatNum( s )
return lang:formatNum(s)
end
end


Line 103: Line 103:


-- TODO: Perhaps we should turn this into another module
-- TODO: Perhaps we should turn this into another module
local function getDetailsHTML( data, frame )
local function getDetailsHTML(data, frame)
local summary = frame:extensionTag {
local summary = frame:extensionTag {
name = 'summary',
name = 'summary',
Line 127: Line 127:
--- @param data table
--- @param data table
--- @return string
--- @return string
function methodtable.tableToCommaList( data )
function methodtable.tableToCommaList(data)
if type( data ) == 'table' then
if type(data) == 'table' then
return table.concat( data, ', ' )
return table.concat(data, ', ')
else
else
return data
return data
Line 140: Line 140:
--- @param s2 string|nil
--- @param s2 string|nil
--- @return string|nil
--- @return string|nil
function methodtable.formatRange( s1, s2, formatNum )
function methodtable.formatRange(s1, s2, formatNum)
if s1 == nil and s2 == nil then
if s1 == nil and s2 == nil then
return
return
Line 149: Line 149:
if formatNum then
if formatNum then
if s1 then
if s1 then
s1 = formatNumber( s1 )
s1 = formatNumber(s1)
end
end
if s2 then
if s2 then
s2 = formatNumber( s2 )
s2 = formatNumber(s2)
end
end
end
end
Line 168: Line 168:
--- @param unit string
--- @param unit string
--- @return string|nil
--- @return string|nil
function methodtable.addUnitIfExists( s, unit )
function methodtable.addUnitIfExists(s, unit)
if s == nil then
if s == nil then
return
return
Line 180: Line 180:
--- @param data table {title, desc)
--- @param data table {title, desc)
--- @return string html
--- @return string html
function methodtable.renderMessage( self, data, noInsert )
function methodtable.renderMessage(self, data, noInsert)
checkType( 'Module:InfoboxNeue.renderMessage', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderMessage', 1, self, 'table')
checkType( 'Module:InfoboxNeue.renderMessage', 2, data, 'table' )
checkType('Module:InfoboxNeue.renderMessage', 2, data, 'table')
checkType( 'Module:InfoboxNeue.renderMessage', 3, noInsert, 'boolean', true )
checkType('Module:InfoboxNeue.renderMessage', 3, noInsert, 'boolean', true)


noInsert = noInsert or false
noInsert = noInsert or false


local item = self:renderSection( { content = self:renderItem( { data = data.title, desc = data.desc } ) }, noInsert )
local item = self:renderSection({ content = self:renderItem({ data = data.title, desc = data.desc }) }, noInsert)


if not noInsert then
if not noInsert then
table.insert( self.entries, item )
table.insert(self.entries, item)
end
end


Line 200: Line 200:
--- @param filename string
--- @param filename string
--- @return string html
--- @return string html
function methodtable.renderImage( self, filename )
function methodtable.renderImage(self, filename)
checkType( 'Module:InfoboxNeue.renderImage', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderImage', 1, self, 'table')


local hasPlaceholderImage = false
local hasPlaceholderImage = false


if type( filename ) ~= 'string' and self.config.displayPlaceholder == true then
if type(filename) ~= 'string' and self.config.displayPlaceholder == true then
hasPlaceholderImage = true
hasPlaceholderImage = true
filename = self.config.placeholderImage
filename = self.config.placeholderImage
-- Add tracking category for infoboxes using placeholder image
-- Add tracking category for infoboxes using placeholder image
table.insert( self.categories,
table.insert(self.categories,
string.format( '[[Category:%s]]', t( 'category_infobox_using_placeholder_image' ) )
string.format('[[Category:%s]]', t('category_infobox_using_placeholder_image'))
)
)
end
end


if type( filename ) ~= 'string' then
if type(filename) ~= 'string' then
return ''
return ''
end
end


local parts = mw.text.split( filename, ':', true )
local parts = mw.text.split(filename, ':', true)
if #parts > 1 then
if #parts > 1 then
table.remove( parts, 1 )
table.remove(parts, 1)
filename = table.concat( parts, ':' )
filename = table.concat(parts, ':')
end
end


local html = mw.html.create( 'div' )
local html = mw.html.create('div')
:addClass( 'infobox__image' )
:addClass('infobox__image')
:wikitext( string.format( '[[File:%s|400px]]', filename ) )
:wikitext(string.format('[[File:%s|400px]]', filename))


if hasPlaceholderImage == true then
if hasPlaceholderImage == true then
local icon = mw.html.create( 'span' ):addClass( 'citizen-ui-icon mw-ui-icon-wikimedia-upload' )
local icon = mw.html.create('span'):addClass('citizen-ui-icon mw-ui-icon-wikimedia-upload')
-- TODO: Point the Upload link to a specific file name
-- TODO: Point the Upload link to a specific file name
html:tag( 'div' ):addClass( 'infobox__image-upload' )
html:tag('div'):addClass('infobox__image-upload')
:wikitext( string.format( '[[%s|%s]]', 'Special:UploadWizard',
:wikitext(string.format('[[%s|%s]]', 'Special:UploadWizard',
tostring( icon ) .. t( 'label_upload_image' ) ) )
tostring(icon) .. t('label_upload_image')))
end
end


local item = tostring( html )
local item = tostring(html)


table.insert( self.entries, item )
table.insert(self.entries, item)


return item
return item
Line 247: Line 247:
--- @param data table {data, class, color, nopadding)
--- @param data table {data, class, color, nopadding)
--- @return string html
--- @return string html
function methodtable.renderIndicator( self, data )
function methodtable.renderIndicator(self, data)
checkType( 'Module:InfoboxNeue.renderIndicator', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderIndicator', 1, self, 'table')
checkType( 'Module:InfoboxNeue.renderIndicator', 2, data, 'table' )
checkType('Module:InfoboxNeue.renderIndicator', 2, data, 'table')


if data == nil or data[ 'data' ] == nil or data[ 'data' ] == '' then return '' end
if data == nil or data['data'] == nil or data['data'] == '' then return '' end


local html = mw.html.create( 'div' ):addClass( 'infobox__indicators' )
local html = mw.html.create('div'):addClass('infobox__indicators')


local htmlClasses = {
local htmlClasses = {
Line 259: Line 259:
}
}


if data[ 'class' ] then
if data['class'] then
table.insert( htmlClasses, data[ 'class' ] )
table.insert(htmlClasses, data['class'])
end
end


if data[ 'color' ] then
if data['color'] then
table.insert( htmlClasses, 'infobox__indicator--' .. data[ 'color' ] )
table.insert(htmlClasses, 'infobox__indicator--' .. data['color'])
end
end


if data[ 'nopadding' ] == true then
if data['nopadding'] == true then
table.insert( htmlClasses, 'infobox__indicator--nopadding' )
table.insert(htmlClasses, 'infobox__indicator--nopadding')
end
end


Line 274: Line 274:
self:renderItem(
self:renderItem(
{
{
[ 'data' ] = data[ 'data' ],
['data'] = data['data'],
[ 'class' ] = table.concat( htmlClasses, ' ' ),
['class'] = table.concat(htmlClasses, ' '),
row = true,
row = true,
spacebetween = true
spacebetween = true
Line 282: Line 282:
)
)


local item = tostring( html )
local item = tostring(html)


table.insert( self.entries, item )
table.insert(self.entries, item)


return item
return item
Line 293: Line 293:
--- @param data table {title, subtitle, badge)
--- @param data table {title, subtitle, badge)
--- @return string html
--- @return string html
function methodtable.renderHeader( self, data )
function methodtable.renderHeader(self, data)
checkType( 'Module:InfoboxNeue.renderHeader', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderHeader', 1, self, 'table')
checkTypeMulti( 'Module:InfoboxNeue.renderHeader', 2, data, { 'table', 'string' } )
checkTypeMulti('Module:InfoboxNeue.renderHeader', 2, data, { 'table', 'string' })


if type( data ) == 'string' then
if type(data) == 'string' then
data = {
data = {
title = data
title = data
Line 303: Line 303:
end
end


if data == nil or data[ 'title' ] == nil then return '' end
if data == nil or data['title'] == nil then return '' end


local html = mw.html.create( 'div' ):addClass( 'infobox__header' )
local html = mw.html.create('div'):addClass('infobox__header')


if data[ 'badge' ] then
if data['badge'] then
html:tag( 'div' )
html:tag('div')
:addClass( 'infobox__item infobox__badge' )
:addClass('infobox__item infobox__badge')
:wikitext( data[ 'badge' ] )
:wikitext(data['badge'])
end
end


local titleItem = mw.html.create( 'div' ):addClass( 'infobox__item' )
local titleItem = mw.html.create('div'):addClass('infobox__item')


titleItem:tag( 'div' )
titleItem:tag('div')
:addClass( 'infobox__title' )
:addClass('infobox__title')
:wikitext( data[ 'title' ] )
:wikitext(data['title'])


if data[ 'subtitle' ] then
if data['subtitle'] then
titleItem:tag( 'div' )
titleItem:tag('div')
-- Subtitle is always data
-- Subtitle is always data
:addClass( 'infobox__subtitle infobox__data' )
:addClass('infobox__subtitle infobox__data')
:wikitext( data[ 'subtitle' ] )
:wikitext(data['subtitle'])
end
end


html:node( titleItem )
html:node(titleItem)


local item = tostring( html )
local item = tostring(html)


table.insert( self.entries, item )
table.insert(self.entries, item)


return item
return item
Line 340: Line 340:
--- @param noInsert boolean whether to insert this section into the internal table table
--- @param noInsert boolean whether to insert this section into the internal table table
--- @return string html
--- @return string html
function methodtable.renderSection( self, data, noInsert )
function methodtable.renderSection(self, data, noInsert)
checkType( 'Module:InfoboxNeue.renderSection', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderSection', 1, self, 'table')
checkType( 'Module:InfoboxNeue.renderSection', 2, data, 'table' )
checkType('Module:InfoboxNeue.renderSection', 2, data, 'table')
checkType( 'Module:InfoboxNeue.renderSection', 3, noInsert, 'boolean', true )
checkType('Module:InfoboxNeue.renderSection', 3, noInsert, 'boolean', true)


noInsert = noInsert or false
noInsert = noInsert or false


if type( data.content ) == 'table' then
if type(data.content) == 'table' then
data.content = table.concat( data.content )
data.content = table.concat(data.content)
end
end


if data == nil or data[ 'content' ] == nil or data[ 'content' ] == '' then return '' end
if data == nil or data['content'] == nil or data['content'] == '' then return '' end


local html = mw.html.create( 'div' ):addClass( 'infobox__section' )
local html = mw.html.create('div'):addClass('infobox__section')


if data[ 'title' ] then
if data['title'] then
local header = html:tag( 'div' ):addClass( 'infobox__sectionHeader' )
local header = html:tag('div'):addClass('infobox__sectionHeader')
header:tag( 'div' )
header:tag('div')
:addClass( 'infobox__sectionTitle' )
:addClass('infobox__sectionTitle')
:wikitext( data[ 'title' ] )
:wikitext(data['title'])
if data[ 'subtitle' ] then
if data['subtitle'] then
header:tag( 'div' )
header:tag('div')
:addClass( 'infobox__sectionSubtitle' )
:addClass('infobox__sectionSubtitle')
:wikitext( data[ 'subtitle' ] )
:wikitext(data['subtitle'])
end
end
end
end


local content = html:tag( 'div' )
local content = html:tag('div')
content:addClass( 'infobox__sectionContent' )
content:addClass('infobox__sectionContent')
:wikitext( data[ 'content' ] )
:wikitext(data['content'])


if data[ 'border' ] == false then html:addClass( 'infobox__section--noborder' ) end
if data['border'] == false then html:addClass('infobox__section--noborder') end
if data[ 'col' ] then content:addClass( 'infobox__grid--cols-' .. data[ 'col' ] ) end
if data['col'] then content:addClass('infobox__grid--cols-' .. data['col']) end
if data[ 'class' ] then html:addClass( data[ 'class' ] ) end
if data['class'] then html:addClass(data['class']) end


local item = tostring( html )
local item = tostring(html)


if not noInsert then
if not noInsert then
table.insert( self.entries, item )
table.insert(self.entries, item)
end
end


Line 388: Line 388:
--- @param data table {label, link, page}
--- @param data table {label, link, page}
--- @return string html
--- @return string html
function methodtable.renderLinkButton( self, data )
function methodtable.renderLinkButton(self, data)
checkType( 'Module:InfoboxNeue.renderLinkButton', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderLinkButton', 1, self, 'table')
checkType( 'Module:InfoboxNeue.renderLinkButton', 2, data, 'table' )
checkType('Module:InfoboxNeue.renderLinkButton', 2, data, 'table')


if data == nil or data[ 'label' ] == nil or (data[ 'link' ] == nil and data[ 'page' ] == nil) then return '' end
if data == nil or data['label'] == nil or (data['link'] == nil and data['page'] == nil) then return '' end


--- Render multiple linkButton when link is a table
--- Render multiple linkButton when link is a table
if type( data[ 'link' ] ) == 'table' then
if type(data['link']) == 'table' then
local htmls = {}
local htmls = {}


for i, url in ipairs( data[ 'link' ] ) do
for i, url in ipairs(data['link']) do
table.insert( htmls,
table.insert(htmls,
self:renderLinkButton( {
self:renderLinkButton({
label = string.format( '%s %d', data[ 'label' ], i ),
label = string.format('%s %d', data['label'], i),
link = url
link = url
} )
})
)
)
end
end


return table.concat( htmls )
return table.concat(htmls)
end
end


local html = mw.html.create( 'div' ):addClass( 'infobox__linkButton' )
local html = mw.html.create('div'):addClass('infobox__linkButton')


if data[ 'link' ] then
if data['link'] then
html:wikitext( string.format( '[%s %s]', restoreUnderscore( data[ 'link' ] ), data[ 'label' ] ) )
html:wikitext(string.format('[%s %s]', restoreUnderscore(data['link']), data['label']))
elseif data[ 'page' ] then
elseif data['page'] then
html:wikitext( string.format( '[[%s|%s]]', data[ 'page' ], data[ 'label' ] ) )
html:wikitext(string.format('[[%s|%s]]', data['page'], data['label']))
end
end


return tostring( html )
return tostring(html)
end
end


Line 425: Line 425:
--- @param data table {content, button}
--- @param data table {content, button}
--- @return string html
--- @return string html
function methodtable.renderFooter( self, data )
function methodtable.renderFooter(self, data)
checkType( 'Module:InfoboxNeue.renderFooter', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderFooter', 1, self, 'table')
checkType( 'Module:InfoboxNeue.renderFooter', 2, data, 'table' )
checkType('Module:InfoboxNeue.renderFooter', 2, data, 'table')


if data == nil then return '' end
if data == nil then return '' end


-- Checks if an input is of type 'table' or 'string' and if it is not empty
-- Checks if an input is of type 'table' or 'string' and if it is not empty
local function isNonEmpty( input )
local function isNonEmpty(input)
return (type( input ) == 'table' and next( input ) ~= nil) or (type( input ) == 'string' and #input > 0)
return (type(input) == 'table' and next(input) ~= nil) or (type(input) == 'string' and #input > 0)
end
end


local hasContent = isNonEmpty( data[ 'content' ] )
local hasContent = isNonEmpty(data['content'])
local hasButton = isNonEmpty( data[ 'button' ] ) and isNonEmpty( data[ 'button' ][ 'content' ] ) and
local hasButton = isNonEmpty(data['button']) and isNonEmpty(data['button']['content']) and
isNonEmpty( data[ 'button' ][ 'label' ] )
isNonEmpty(data['button']['label'])


if not hasContent and not hasButton then return '' end
if not hasContent and not hasButton then return '' end


local html = mw.html.create( 'div' ):addClass( 'infobox__footer' )
local html = mw.html.create('div'):addClass('infobox__footer')


if hasContent then
if hasContent then
local content = data[ 'content' ]
local content = data['content']
if type( content ) == 'table' then content = table.concat( content ) end
if type(content) == 'table' then content = table.concat(content) end


html:addClass( 'infobox__footer--has-content' )
html:addClass('infobox__footer--has-content')
html:tag( 'div' )
html:tag('div')
:addClass( 'infobox__section' )
:addClass('infobox__section')
:wikitext( content )
:wikitext(content)
end
end


if hasButton then
if hasButton then
html:addClass( 'infobox__footer--has-button' )
html:addClass('infobox__footer--has-button')
local buttonData = data[ 'button' ]
local buttonData = data['button']
local button = html:tag( 'div' ):addClass( 'infobox__button' )
local button = html:tag('div'):addClass('infobox__button')
local label = button:tag( 'div' ):addClass( 'infobox__buttonLabel' )
local label = button:tag('div'):addClass('infobox__buttonLabel')


if buttonData[ 'icon' ] ~= nil then
if buttonData['icon'] ~= nil then
label:wikitext( string.format( '[[File:%s|16px|link=]]%s', buttonData[ 'icon' ], buttonData[ 'label' ] ) )
label:wikitext(string.format('[[File:%s|16px|link=]]%s', buttonData['icon'], buttonData['label']))
else
else
label:wikitext( buttonData[ 'label' ] )
label:wikitext(buttonData['label'])
end
end


if buttonData[ 'type' ] == 'link' then
if buttonData['type'] == 'link' then
button:tag( 'div' )
button:tag('div')
:addClass( 'infobox__buttonLink' )
:addClass('infobox__buttonLink')
:wikitext( buttonData[ 'content' ] )
:wikitext(buttonData['content'])
elseif buttonData[ 'type' ] == 'popup' then
elseif buttonData['type'] == 'popup' then
button:tag( 'div' )
button:tag('div')
:addClass( 'infobox__buttonCard' )
:addClass('infobox__buttonCard')
:wikitext( buttonData[ 'content' ] )
:wikitext(buttonData['content'])
end
end
end
end


local item = tostring( html )
local item = tostring(html)


table.insert( self.entries, item )
table.insert(self.entries, item)


return item
return item
Line 488: Line 488:
--- @param data table {icon, label, type, content}
--- @param data table {icon, label, type, content}
--- @return string html
--- @return string html
function methodtable.renderFooterButton( self, data )
function methodtable.renderFooterButton(self, data)
checkType( 'Module:InfoboxNeue.renderFooterButton', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderFooterButton', 1, self, 'table')
checkType( 'Module:InfoboxNeue.renderFooterButton', 2, data, 'table' )
checkType('Module:InfoboxNeue.renderFooterButton', 2, data, 'table')


if data == nil then return '' end
if data == nil then return '' end


return self:renderFooter( { button = data } )
return self:renderFooter({ button = data })
end
end


Line 502: Line 502:
--- @param content string|number|nil optional
--- @param content string|number|nil optional
--- @return string html
--- @return string html
function methodtable.renderItem( self, data, content )
function methodtable.renderItem(self, data, content)
checkType( 'Module:InfoboxNeue.renderItem', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderItem', 1, self, 'table')
checkTypeMulti( 'Module:InfoboxNeue.renderItem', 2, data, { 'table', 'string' } )
checkTypeMulti('Module:InfoboxNeue.renderItem', 2, data, { 'table', 'string' })
checkTypeMulti( 'Module:InfoboxNeue.renderItem', 3, content, { 'string', 'number', 'nil' } )
checkTypeMulti('Module:InfoboxNeue.renderItem', 3, content, { 'string', 'number', 'nil' })


-- The arguments are not passed as a table
-- The arguments are not passed as a table
Line 516: Line 516:
end
end


if data == nil or data[ 'data' ] == nil or data[ 'data' ] == '' then return '' end
if data == nil or data['data'] == nil or data['data'] == '' then return '' end


if self.config.removeEmpty == true and data[ 'data' ] == self.config.emptyString then
if self.config.removeEmpty == true and data['data'] == self.config.emptyString then
return ''
return ''
end
end


local html = mw.html.create( 'div' ):addClass( 'infobox__item' )
local html = mw.html.create('div'):addClass('infobox__item')


if data[ 'class' ] then html:addClass( data[ 'class' ] ) end
if data['class'] then html:addClass(data['class']) end
if data[ 'tooltip' ] then html:attr( 'title', data[ 'tooltip' ] ) end
if data['tooltip'] then html:attr('title', data['tooltip']) end
if data[ 'row' ] == true then html:addClass( 'infobox__grid--row' ) end
if data['row'] == true then html:addClass('infobox__grid--row') end
if data[ 'spacebetween' ] == true then html:addClass( 'infobox__grid--space-between' ) end
if data['spacebetween'] == true then html:addClass('infobox__grid--space-between') end
if data[ 'colspan' ] then html:addClass( 'infobox__grid--col-span-' .. data[ 'colspan' ] ) end
if data['colspan'] then html:addClass('infobox__grid--col-span-' .. data['colspan']) end


local textWrapper = html
local textWrapper = html


if data[ 'link' ] then
if data['link'] then
html:addClass( 'infobox__itemButton' )
html:addClass('infobox__itemButton')
html:tag( 'div' )
html:tag('div')
:addClass( 'infobox__itemButtonLink' )
:addClass('infobox__itemButtonLink')
:wikitext( string.format( '[%s]', data[ 'link' ] ) )
:wikitext(string.format('[%s]', data['link']))
elseif data[ 'page' ] then
elseif data['page'] then
html:addClass( 'infobox__itemButton' )
html:addClass('infobox__itemButton')
html:tag( 'div' )
html:tag('div')
:addClass( 'infobox__itemButtonLink' )
:addClass('infobox__itemButtonLink')
:wikitext( string.format( '[[%s]]', data[ 'link' ] ) )
:wikitext(string.format('[[%s]]', data['link']))
end
end


if data[ 'icon' ] then
if data['icon'] then
html:addClass( 'infobox__item--hasIcon' )
html:addClass('infobox__item--hasIcon')
html:tag( 'div' )
html:tag('div')
:addClass( 'infobox__icon' )
:addClass('infobox__icon')
:wikitext( string.format( '[[File:%s|16px|link=]]', data[ 'icon' ] ) )
:wikitext(string.format('[[File:%s|16px|link=]]', data['icon']))
-- Create wrapper for text to align with icon
-- Create wrapper for text to align with icon
textWrapper = html:tag( 'div' ):addClass( 'infobox__text' )
textWrapper = html:tag('div'):addClass('infobox__text')
end
end


local dataOrder = { 'label', 'data', 'desc' }
local dataOrder = { 'label', 'data', 'desc' }


for _, key in ipairs( dataOrder ) do
for _, key in ipairs(dataOrder) do
if data[ key ] then
if data[key] then
if type( data[ key ] ) == 'table' then
if type(data[key]) == 'table' then
data[ key ] = table.concat( data[ key ], ', ' )
data[key] = table.concat(data[key], ', ')
end
end


local actualData = data[ key ]
local actualData = data[key]


if key == 'data' and type( string.find( data[ key ], '^%*' ) ) ~= 'nil' then
if key == 'data' and type(string.find(data[key], '^%*')) ~= 'nil' then
actualData = "\n\n" .. data[ key ]
actualData = "\n\n" .. data[key]
end
end


textWrapper:tag( 'div' )
textWrapper:tag('div')
:addClass( 'infobox__' .. key )
:addClass('infobox__' .. key)
:wikitext( actualData )
:wikitext(actualData)
end
end
end
end


-- Add arrow indicator as affordnance
-- Add arrow indicator as affordnance
if data[ 'link' ] or data[ 'page' ] then
if data['link'] or data['page'] then
html:tag( 'div' ):addClass( 'infobox__itemButtonArrow citizen-ui-icon mw-ui-icon-wikimedia-collapse' )
html:tag('div'):addClass('infobox__itemButtonArrow citizen-ui-icon mw-ui-icon-wikimedia-collapse')
end
end


return tostring( html )
return tostring(html)
end
end


Line 586: Line 586:
--- @param snippetText string text used in snippet in mobile view
--- @param snippetText string text used in snippet in mobile view
--- @return string html infobox html with templatestyles
--- @return string html infobox html with templatestyles
function methodtable.renderInfobox( self, innerHtml, snippetText )
function methodtable.renderInfobox(self, innerHtml, snippetText)
checkType( 'Module:InfoboxNeue.renderInfobox', 1, self, 'table' )
checkType('Module:InfoboxNeue.renderInfobox', 1, self, 'table')
checkTypeMulti( 'Module:InfoboxNeue.renderInfobox', 2, innerHtml, { 'table', 'string', 'nil' } )
checkTypeMulti('Module:InfoboxNeue.renderInfobox', 2, innerHtml, { 'table', 'string', 'nil' })
checkType( 'Module:InfoboxNeue.renderInfobox', 3, snippetText, 'string', true )
checkType('Module:InfoboxNeue.renderInfobox', 3, snippetText, 'string', true)


innerHtml = innerHtml or self.entries
innerHtml = innerHtml or self.entries
if type( innerHtml ) == 'table' then
if type(innerHtml) == 'table' then
innerHtml = table.concat( self.entries )
innerHtml = table.concat(self.entries)
end
end


local function renderContent()
local function renderContent()
local html = mw.html.create( 'div' )
local html = mw.html.create('div')
:addClass( 'infobox__content' )
:addClass('infobox__content')
:wikitext( innerHtml )
:wikitext(innerHtml)
return tostring( html )
return tostring(html)
end
end


Line 607: Line 607:


local html = mw.html.create()
local html = mw.html.create()

html:tag( 'div' )
html:tag('div')
:addClass( 'citizen-ui-icon mw-ui-icon-wikimedia-collapse' )
:addClass('citizen-ui-icon mw-ui-icon-wikimedia-collapse')
:done()
:done()
:tag( 'div' )
:tag('div')
:addClass( 'infobox__data' )
:addClass('infobox__data')
:wikitext( string.format( '%s:', t( 'label_quick_facts' ) ) )
:wikitext(string.format('%s:', t('label_quick_facts')))
:done()
:done()
:tag( 'div' )
:tag('div')
:addClass( 'infobox__desc' )
:addClass('infobox__desc')
:wikitext( snippetText )
:wikitext(snippetText)


return tostring( html )
return tostring(html)
end
end


local frame = mw.getCurrentFrame()
local frame = mw.getCurrentFrame()
local output = getDetailsHTML( {
local output = getDetailsHTML({
details = {
details = {
class = 'infobox floatright noexcerpt',
class = 'infobox floatright noexcerpt',
Line 632: Line 632:
content = renderSnippet()
content = renderSnippet()
}
}
}, frame )
}, frame)


return frame:extensionTag {
return frame:extensionTag {
name = 'templatestyles', args = { src = 'Module:InfoboxNeue/styles.css' }
name = 'templatestyles', args = { src = 'Module:InfoboxNeue/styles.css' }
} .. output .. table.concat( self.categories )
} .. output .. table.concat(self.categories)
end
end


--- Just an accessor for the class method
--- Just an accessor for the class method
function methodtable.showDescIfDiff( s1, s2 )
function methodtable.showDescIfDiff(s1, s2)
return InfoboxNeue.showDescIfDiff( s1, s2 )
return InfoboxNeue.showDescIfDiff(s1, s2)
end
end


Line 649: Line 649:
--- @param s2 string|nil comparsion
--- @param s2 string|nil comparsion
--- @return string|nil html
--- @return string|nil html
function InfoboxNeue.showDescIfDiff( s1, s2 )
function InfoboxNeue.showDescIfDiff(s1, s2)
if s1 == nil or s2 == nil or s1 == s2 then return s1 end
if s1 == nil or s2 == nil or s1 == s2 then return s1 end
return string.format( '%s <span class="infobox__desc">(%s)</span>', s1, s2 )
return string.format('%s <span class="infobox__desc">(%s)</span>', s1, s2)
end
end


Line 657: Line 657:
---
---
--- @return table InfoboxNeue
--- @return table InfoboxNeue
function InfoboxNeue.new( self, config )
function InfoboxNeue.new(self, config)
local baseConfig = {
local baseConfig = {
-- Flag to discard empty rows
-- Flag to discard empty rows
Line 669: Line 669:
}
}


for k, v in pairs( config or {} ) do
for k, v in pairs(config or {}) do
baseConfig[ k ] = v
baseConfig[k] = v
end
end


Line 679: Line 679:
}
}


setmetatable( instance, metatable )
setmetatable(instance, metatable)


return instance
return instance
Line 688: Line 688:
--- @param frame table
--- @param frame table
--- @return string
--- @return string
function InfoboxNeue.fromArgs( frame )
function InfoboxNeue.fromArgs(frame)
local instance = InfoboxNeue:new()
local instance = InfoboxNeue:new()
local args = require( 'Module:Arguments' ).getArgs( frame )
local args = require('Module:Arguments').getArgs(frame)


local sections = {
local sections = {
Line 700: Line 700:
local currentSection
local currentSection


if args[ 'image' ] then
if args['image'] then
instance:renderImage( args[ 'image' ] )
instance:renderImage(args['image'])
if args[ 'indicator' ] then
if args['indicator'] then
instance:renderIndicator( {
instance:renderIndicator({
data = args[ 'indicator' ],
data = args['indicator'],
class = args[ 'indicatorClass' ]
class = args['indicatorClass']
} )
})
end
end
else
else
for i = 1, 3, 1 do
for i = 1, 3, 1 do
if args[ 'image' .. i ] then
if args['image' .. i] then
instance:renderImage( args[ 'image' .. i ] )
instance:renderImage(args['image' .. i])
if args[ 'indicator' .. i ] then
if args['indicator' .. i] then
instance:renderIndicator( {
instance:renderIndicator({
data = args[ 'indicator' .. i ],
data = args['indicator' .. i],
class = args[ 'indicatorClass' .. i ]
class = args['indicatorClass' .. i]
} )
})
end
end
end
end
Line 722: Line 722:
end
end


if args[ 'title' ] then
if args['title'] then
instance:renderHeader( {
instance:renderHeader({
title = args[ 'title' ],
title = args['title'],
subtitle = args[ 'subtitle' ],
subtitle = args['subtitle'],
} )
})
end
end


for key, value in orderedPairs( args ) do
for key, value in orderedPairs(args) do
if type( string.match( key, '^content' ) ) ~= 'nil' then
if type(string.match(key, '^label')) ~= 'nil' then
if args[ 'section' .. removeprefix( key, 'content' ) ] then
if args['section' .. removeprefix(key, 'label')] then
currentSection = args[ 'section' .. removeprefix( key, 'content' ) ]
currentSection = args['section' .. removeprefix(key, 'label')]


table.insert( sections, {
table.insert(sections, {
title = currentSection,
title = currentSection,
subtitle = args[ 'section-subtitle' .. removeprefix( key, 'content' ) ],
subtitle = args['section-subtitle' .. removeprefix(key, 'label')],
col = args[ 'section-col' .. removeprefix( key, 'content' ) ] or 2,
col = args['section-col' .. removeprefix(key, 'label')] or 2,
content = {}
content = {}
} )
})


sectionMap[ currentSection ] = #sections
sectionMap[currentSection] = #sections
end
end


local data = {
local data = {
label = args[ 'label' .. removeprefix( key, 'content' ) ],
label = value,
data = args['content' .. removeprefix(key, 'label')]
data = value
}
}


table.insert( sections[ sectionMap[ (currentSection or 'default') ] ].content,
table.insert(sections[sectionMap[(currentSection or 'default')]].content,
instance:renderItem( data, nil ) )
instance:renderItem(data, nil))
end
end
end
end


for _, section in ipairs( sections ) do
for _, section in ipairs(sections) do
instance:renderSection( section )
instance:renderSection(section)
end
end


return instance:renderInfobox( nil, args[ 'snippet' ] )
return instance:renderInfobox(nil, args['snippet'])
end
end



Revision as of 18:30, December 30, 2024

This module is used for Template:InfoboxNeue. See the template's page for more information.


local InfoboxNeue = {}

local metatable = {}
local methodtable = {}

local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
local i18n = require('Module:i18n'):new()

metatable.__index = methodtable

metatable.__tostring = function(self)
	return tostring(self:renderInfobox())
end

-- http://lua-users.org/wiki/SortedIteration
local function __genOrderedIndex(t)
	local orderedIndex = {}
	for key in pairs(t) do
		table.insert(orderedIndex, key)
	end
	table.sort(orderedIndex)
	return orderedIndex
end

local function orderedNext(t, state)
	-- Equivalent of the next function, but returns the keys in the alphabetic
	-- order. We use a temporary ordered key table that is stored in the
	-- table being iterated.

	local key = nil
	--print("orderedNext: state = "..tostring(state) )
	if state == nil then
		-- the first time, generate the index
		t.__orderedIndex = __genOrderedIndex(t)
		key = t.__orderedIndex[1]
	else
		-- fetch the next value
		for i = 1, table.getn(t.__orderedIndex) do
			if t.__orderedIndex[i] == state then
				key = t.__orderedIndex[i + 1]
			end
		end
	end

	if key then
		return key, t[key]
	end

	-- no more value to return, cleanup
	t.__orderedIndex = nil
	return
end

local function orderedPairs(t)
	-- Equivalent of the pairs() function on tables. Allows to iterate
	-- in order
	return orderedNext, t, nil
end


--- Wrapper function for Module:i18n.translate
---
--- @param key string The translation key
--- @return string If the key was not found, the key is returned
local function t(key)
	return i18n:translate(key)
end


--- Helper function to restore underscore from space
--- so that it does not screw up the external link wikitext syntax
--- For some reason SMW property converts underscore into space
--- mw.uri.encode can't be used on full URL
local function restoreUnderscore(s)
	return s:gsub(' ', '%%5F')
end

local function removeprefix(s, p)
	return (s:sub(0, #p) == p) and s:sub(#p + 1) or s
end


--- Helper function to format string to number with separators
--- It is usually use to re-format raw number from SMW into more readable format
local function formatNumber(s)
	local lang = mw.getContentLanguage()
	if s == nil then
		return
	end

	if type(s) ~= 'number' then
		s = tonumber(s)
	end

	if type(s) == 'number' then
		return lang:formatNum(s)
	end

	return s
end

-- TODO: Perhaps we should turn this into another module
local function getDetailsHTML(data, frame)
	local summary = frame:extensionTag {
		name = 'summary',
		content = data.summary.content,
		args = {
			class = data.summary.class
		}
	}
	local details = frame:extensionTag {
		name = 'details',
		content = summary .. data.details.content,
		args = {
			class = data.details.class,
			open = true
		}
	}
	return details
end


--- Put table values into a comma-separated list
---
--- @param data table
--- @return string
function methodtable.tableToCommaList(data)
	if type(data) == 'table' then
		return table.concat(data, ', ')
	else
		return data
	end
end

--- Show range if value1 and value2 are different
---
--- @param s1 string|nil
--- @param s2 string|nil
--- @return string|nil
function methodtable.formatRange(s1, s2, formatNum)
	if s1 == nil and s2 == nil then
		return
	end

	formatNum = formatNum or false

	if formatNum then
		if s1 then
			s1 = formatNumber(s1)
		end
		if s2 then
			s2 = formatNumber(s2)
		end
	end

	if s1 and s2 and s1 ~= s2 then
		return s1 .. ' – ' .. s2
	end

	return s1 or s2
end

--- Append unit to the value if exists
---
--- @param s string
--- @param unit string
--- @return string|nil
function methodtable.addUnitIfExists(s, unit)
	if s == nil then
		return
	end

	return s .. ' ' .. unit
end

--- Shortcut to return the HTML of the infobox message component as string
---
--- @param data table {title, desc)
--- @return string html
function methodtable.renderMessage(self, data, noInsert)
	checkType('Module:InfoboxNeue.renderMessage', 1, self, 'table')
	checkType('Module:InfoboxNeue.renderMessage', 2, data, 'table')
	checkType('Module:InfoboxNeue.renderMessage', 3, noInsert, 'boolean', true)

	noInsert = noInsert or false

	local item = self:renderSection({ content = self:renderItem({ data = data.title, desc = data.desc }) }, noInsert)

	if not noInsert then
		table.insert(self.entries, item)
	end

	return item
end

--- Return the HTML of the infobox image component as string
---
--- @param filename string
--- @return string html
function methodtable.renderImage(self, filename)
	checkType('Module:InfoboxNeue.renderImage', 1, self, 'table')

	local hasPlaceholderImage = false

	if type(filename) ~= 'string' and self.config.displayPlaceholder == true then
		hasPlaceholderImage = true
		filename = self.config.placeholderImage
		-- Add tracking category for infoboxes using placeholder image
		table.insert(self.categories,
			string.format('[[Category:%s]]', t('category_infobox_using_placeholder_image'))
		)
	end

	if type(filename) ~= 'string' then
		return ''
	end

	local parts = mw.text.split(filename, ':', true)
	if #parts > 1 then
		table.remove(parts, 1)
		filename = table.concat(parts, ':')
	end

	local html = mw.html.create('div')
		:addClass('infobox__image')
		:wikitext(string.format('[[File:%s|400px]]', filename))

	if hasPlaceholderImage == true then
		local icon = mw.html.create('span'):addClass('citizen-ui-icon mw-ui-icon-wikimedia-upload')
		-- TODO: Point the Upload link to a specific file name
		html:tag('div'):addClass('infobox__image-upload')
			:wikitext(string.format('[[%s|%s]]', 'Special:UploadWizard',
				tostring(icon) .. t('label_upload_image')))
	end

	local item = tostring(html)

	table.insert(self.entries, item)

	return item
end

--- Return the HTML of the infobox indicator component as string
---
--- @param data table {data, class, color, nopadding)
--- @return string html
function methodtable.renderIndicator(self, data)
	checkType('Module:InfoboxNeue.renderIndicator', 1, self, 'table')
	checkType('Module:InfoboxNeue.renderIndicator', 2, data, 'table')

	if data == nil or data['data'] == nil or data['data'] == '' then return '' end

	local html = mw.html.create('div'):addClass('infobox__indicators')

	local htmlClasses = {
		'infobox__indicator'
	}

	if data['class'] then
		table.insert(htmlClasses, data['class'])
	end

	if data['color'] then
		table.insert(htmlClasses, 'infobox__indicator--' .. data['color'])
	end

	if data['nopadding'] == true then
		table.insert(htmlClasses, 'infobox__indicator--nopadding')
	end

	html:wikitext(
		self:renderItem(
			{
				['data'] = data['data'],
				['class'] = table.concat(htmlClasses, ' '),
				row = true,
				spacebetween = true
			}
		)
	)

	local item = tostring(html)

	table.insert(self.entries, item)

	return item
end

--- Return the HTML of the infobox header component as string
---
--- @param data table {title, subtitle, badge)
--- @return string html
function methodtable.renderHeader(self, data)
	checkType('Module:InfoboxNeue.renderHeader', 1, self, 'table')
	checkTypeMulti('Module:InfoboxNeue.renderHeader', 2, data, { 'table', 'string' })

	if type(data) == 'string' then
		data = {
			title = data
		}
	end

	if data == nil or data['title'] == nil then return '' end

	local html = mw.html.create('div'):addClass('infobox__header')

	if data['badge'] then
		html:tag('div')
			:addClass('infobox__item infobox__badge')
			:wikitext(data['badge'])
	end

	local titleItem = mw.html.create('div'):addClass('infobox__item')

	titleItem:tag('div')
		:addClass('infobox__title')
		:wikitext(data['title'])

	if data['subtitle'] then
		titleItem:tag('div')
		-- Subtitle is always data
			:addClass('infobox__subtitle infobox__data')
			:wikitext(data['subtitle'])
	end

	html:node(titleItem)

	local item = tostring(html)

	table.insert(self.entries, item)

	return item
end

--- Wrap the HTML into an infobox section
---
--- @param data table {title, subtitle, content, border, col, class}
--- @param noInsert boolean whether to insert this section into the internal table table
--- @return string html
function methodtable.renderSection(self, data, noInsert)
	checkType('Module:InfoboxNeue.renderSection', 1, self, 'table')
	checkType('Module:InfoboxNeue.renderSection', 2, data, 'table')
	checkType('Module:InfoboxNeue.renderSection', 3, noInsert, 'boolean', true)

	noInsert = noInsert or false

	if type(data.content) == 'table' then
		data.content = table.concat(data.content)
	end

	if data == nil or data['content'] == nil or data['content'] == '' then return '' end

	local html = mw.html.create('div'):addClass('infobox__section')

	if data['title'] then
		local header = html:tag('div'):addClass('infobox__sectionHeader')
		header:tag('div')
			:addClass('infobox__sectionTitle')
			:wikitext(data['title'])
		if data['subtitle'] then
			header:tag('div')
				:addClass('infobox__sectionSubtitle')
				:wikitext(data['subtitle'])
		end
	end

	local content = html:tag('div')
	content:addClass('infobox__sectionContent')
		:wikitext(data['content'])

	if data['border'] == false then html:addClass('infobox__section--noborder') end
	if data['col'] then content:addClass('infobox__grid--cols-' .. data['col']) end
	if data['class'] then html:addClass(data['class']) end

	local item = tostring(html)

	if not noInsert then
		table.insert(self.entries, item)
	end

	return item
end

--- Return the HTML of the infobox link button component as string
---
--- @param data table {label, link, page}
--- @return string html
function methodtable.renderLinkButton(self, data)
	checkType('Module:InfoboxNeue.renderLinkButton', 1, self, 'table')
	checkType('Module:InfoboxNeue.renderLinkButton', 2, data, 'table')

	if data == nil or data['label'] == nil or (data['link'] == nil and data['page'] == nil) then return '' end

	--- Render multiple linkButton when link is a table
	if type(data['link']) == 'table' then
		local htmls = {}

		for i, url in ipairs(data['link']) do
			table.insert(htmls,
				self:renderLinkButton({
					label = string.format('%s %d', data['label'], i),
					link = url
				})
			)
		end

		return table.concat(htmls)
	end

	local html = mw.html.create('div'):addClass('infobox__linkButton')

	if data['link'] then
		html:wikitext(string.format('[%s %s]', restoreUnderscore(data['link']), data['label']))
	elseif data['page'] then
		html:wikitext(string.format('[[%s|%s]]', data['page'], data['label']))
	end

	return tostring(html)
end

--- Return the HTML of the infobox footer component as string
---
--- @param data table {content, button}
--- @return string html
function methodtable.renderFooter(self, data)
	checkType('Module:InfoboxNeue.renderFooter', 1, self, 'table')
	checkType('Module:InfoboxNeue.renderFooter', 2, data, 'table')

	if data == nil then return '' end

	-- Checks if an input is of type 'table' or 'string' and if it is not empty
	local function isNonEmpty(input)
		return (type(input) == 'table' and next(input) ~= nil) or (type(input) == 'string' and #input > 0)
	end

	local hasContent = isNonEmpty(data['content'])
	local hasButton = isNonEmpty(data['button']) and isNonEmpty(data['button']['content']) and
		isNonEmpty(data['button']['label'])

	if not hasContent and not hasButton then return '' end

	local html = mw.html.create('div'):addClass('infobox__footer')

	if hasContent then
		local content = data['content']
		if type(content) == 'table' then content = table.concat(content) end

		html:addClass('infobox__footer--has-content')
		html:tag('div')
			:addClass('infobox__section')
			:wikitext(content)
	end

	if hasButton then
		html:addClass('infobox__footer--has-button')
		local buttonData = data['button']
		local button = html:tag('div'):addClass('infobox__button')
		local label = button:tag('div'):addClass('infobox__buttonLabel')

		if buttonData['icon'] ~= nil then
			label:wikitext(string.format('[[File:%s|16px|link=]]%s', buttonData['icon'], buttonData['label']))
		else
			label:wikitext(buttonData['label'])
		end

		if buttonData['type'] == 'link' then
			button:tag('div')
				:addClass('infobox__buttonLink')
				:wikitext(buttonData['content'])
		elseif buttonData['type'] == 'popup' then
			button:tag('div')
				:addClass('infobox__buttonCard')
				:wikitext(buttonData['content'])
		end
	end

	local item = tostring(html)

	table.insert(self.entries, item)

	return item
end

--- Return the HTML of the infobox footer button component as string
---
--- @param data table {icon, label, type, content}
--- @return string html
function methodtable.renderFooterButton(self, data)
	checkType('Module:InfoboxNeue.renderFooterButton', 1, self, 'table')
	checkType('Module:InfoboxNeue.renderFooterButton', 2, data, 'table')

	if data == nil then return '' end

	return self:renderFooter({ button = data })
end

--- Return the HTML of the infobox item component as string
---
--- @param data table {label, data, desc, class, tooltip, icon, row, spacebetween, colspan)
--- @param content string|number|nil optional
--- @return string html
function methodtable.renderItem(self, data, content)
	checkType('Module:InfoboxNeue.renderItem', 1, self, 'table')
	checkTypeMulti('Module:InfoboxNeue.renderItem', 2, data, { 'table', 'string' })
	checkTypeMulti('Module:InfoboxNeue.renderItem', 3, content, { 'string', 'number', 'nil' })

	-- The arguments are not passed as a table
	-- Allows to call this as box:renderItem( 'Label', 'Data' )
	if content ~= nil then
		data = {
			label = data,
			data = content
		}
	end

	if data == nil or data['data'] == nil or data['data'] == '' then return '' end

	if self.config.removeEmpty == true and data['data'] == self.config.emptyString then
		return ''
	end

	local html = mw.html.create('div'):addClass('infobox__item')

	if data['class'] then html:addClass(data['class']) end
	if data['tooltip'] then html:attr('title', data['tooltip']) end
	if data['row'] == true then html:addClass('infobox__grid--row') end
	if data['spacebetween'] == true then html:addClass('infobox__grid--space-between') end
	if data['colspan'] then html:addClass('infobox__grid--col-span-' .. data['colspan']) end

	local textWrapper = html

	if data['link'] then
		html:addClass('infobox__itemButton')
		html:tag('div')
			:addClass('infobox__itemButtonLink')
			:wikitext(string.format('[%s]', data['link']))
	elseif data['page'] then
		html:addClass('infobox__itemButton')
		html:tag('div')
			:addClass('infobox__itemButtonLink')
			:wikitext(string.format('[[%s]]', data['link']))
	end

	if data['icon'] then
		html:addClass('infobox__item--hasIcon')
		html:tag('div')
			:addClass('infobox__icon')
			:wikitext(string.format('[[File:%s|16px|link=]]', data['icon']))
		-- Create wrapper for text to align with icon
		textWrapper = html:tag('div'):addClass('infobox__text')
	end

	local dataOrder = { 'label', 'data', 'desc' }

	for _, key in ipairs(dataOrder) do
		if data[key] then
			if type(data[key]) == 'table' then
				data[key] = table.concat(data[key], ', ')
			end

			local actualData = data[key]

			if key == 'data' and type(string.find(data[key], '^%*')) ~= 'nil' then
				actualData = "\n\n" .. data[key]
			end

			textWrapper:tag('div')
				:addClass('infobox__' .. key)
				:wikitext(actualData)
		end
	end

	-- Add arrow indicator as affordnance
	if data['link'] or data['page'] then
		html:tag('div'):addClass('infobox__itemButtonArrow citizen-ui-icon mw-ui-icon-wikimedia-collapse')
	end

	return tostring(html)
end

--- Wrap the infobox HTML
---
--- @param innerHtml string inner html of the infobox
--- @param snippetText string text used in snippet in mobile view
--- @return string html infobox html with templatestyles
function methodtable.renderInfobox(self, innerHtml, snippetText)
	checkType('Module:InfoboxNeue.renderInfobox', 1, self, 'table')
	checkTypeMulti('Module:InfoboxNeue.renderInfobox', 2, innerHtml, { 'table', 'string', 'nil' })
	checkType('Module:InfoboxNeue.renderInfobox', 3, snippetText, 'string', true)

	innerHtml = innerHtml or self.entries
	if type(innerHtml) == 'table' then
		innerHtml = table.concat(self.entries)
	end

	local function renderContent()
		local html = mw.html.create('div')
			:addClass('infobox__content')
			:wikitext(innerHtml)
		return tostring(html)
	end

	local function renderSnippet()
		if snippetText == nil then snippetText = mw.title.getCurrentTitle().text end

		local html = mw.html.create()

		html:tag('div')
			:addClass('citizen-ui-icon mw-ui-icon-wikimedia-collapse')
			:done()
			:tag('div')
			:addClass('infobox__data')
			:wikitext(string.format('%s:', t('label_quick_facts')))
			:done()
			:tag('div')
			:addClass('infobox__desc')
			:wikitext(snippetText)

		return tostring(html)
	end

	local frame = mw.getCurrentFrame()
	local output = getDetailsHTML({
		details = {
			class = 'infobox floatright noexcerpt',
			content = renderContent()
		},
		summary = {
			class = 'infobox__snippet',
			content = renderSnippet()
		}
	}, frame)

	return frame:extensionTag {
		name = 'templatestyles', args = { src = 'Module:InfoboxNeue/styles.css' }
	} .. output .. table.concat(self.categories)
end

--- Just an accessor for the class method
function methodtable.showDescIfDiff(s1, s2)
	return InfoboxNeue.showDescIfDiff(s1, s2)
end

--- Format text to show comparison as desc text if two strings are different
---
--- @param s1 string|nil base
--- @param s2 string|nil comparsion
--- @return string|nil html
function InfoboxNeue.showDescIfDiff(s1, s2)
	if s1 == nil or s2 == nil or s1 == s2 then return s1 end
	return string.format('%s <span class="infobox__desc">(%s)</span>', s1, s2)
end

--- New Instance
---
--- @return table InfoboxNeue
function InfoboxNeue.new(self, config)
	local baseConfig = {
		-- Flag to discard empty rows
		removeEmpty = true,
		-- Optional string which is valued as empty
		emptyString = nil,
		-- Display a placeholder image if addImage does not find an image
		displayPlaceholder = true,
		-- Placeholder Image
		placeholderImage = 'Unknown.png',
	}

	for k, v in pairs(config or {}) do
		baseConfig[k] = v
	end

	local instance = {
		categories = {},
		config = baseConfig,
		entries = {}
	}

	setmetatable(instance, metatable)

	return instance
end

--- Create an Infobox from args
---
--- @param frame table
--- @return string
function InfoboxNeue.fromArgs(frame)
	local instance = InfoboxNeue:new()
	local args = require('Module:Arguments').getArgs(frame)

	local sections = {
		{ content = {}, col = 2 }
	}

	local sectionMap = { default = 1 }

	local currentSection

	if args['image'] then
		instance:renderImage(args['image'])
		if args['indicator'] then
			instance:renderIndicator({
				data = args['indicator'],
				class = args['indicatorClass']
			})
		end
	else
		for i = 1, 3, 1 do
			if args['image' .. i] then
				instance:renderImage(args['image' .. i])
				if args['indicator' .. i] then
					instance:renderIndicator({
						data = args['indicator' .. i],
						class = args['indicatorClass' .. i]
					})
				end
			end
		end
	end

	if args['title'] then
		instance:renderHeader({
			title = args['title'],
			subtitle = args['subtitle'],
		})
	end

	for key, value in orderedPairs(args) do
		if type(string.match(key, '^label')) ~= 'nil' then
			if args['section' .. removeprefix(key, 'label')] then
				currentSection = args['section' .. removeprefix(key, 'label')]

				table.insert(sections, {
					title = currentSection,
					subtitle = args['section-subtitle' .. removeprefix(key, 'label')],
					col = args['section-col' .. removeprefix(key, 'label')] or 2,
					content = {}
				})

				sectionMap[currentSection] = #sections
			end

			local data = {
				label = value,
				data = args['content' .. removeprefix(key, 'label')]
			}

			table.insert(sections[sectionMap[(currentSection or 'default')]].content,
				instance:renderItem(data, nil))
		end
	end

	for _, section in ipairs(sections) do
		instance:renderSection(section)
	end

	return instance:renderInfobox(nil, args['snippet'])
end

return InfoboxNeue