From 5fa050af10a058edb0ee457ecc9d9698216b649f Mon Sep 17 00:00:00 2001 From: Rikard Blixt Date: Tue, 15 Oct 2024 09:30:16 +0200 Subject: [PATCH] refactor(widget): migrate remaining widgets to v2 & remove v1 backwards compatability (#4863) * refactor(widget): migrate infobox widgets to v2 * fix deploy name * add flip * re-add missing div * linter * typo * remove self.children * hopefully fix customizable * as self.children is removed * simplify widget * ??? * fix headers * instead of hacking with the injector, let's use a context for it * fix typo * fix typo * fix typo * fix * fix error on links * fix instance * fix ppt * fully fix links * flip the correct one * fix display text * fix breakdown * fix center * respect annos --- components/infobox/commons/infobox_basic.lua | 10 +- .../prize_pool/commons/prize_pool_base.lua | 10 +- .../contexts/widget_contexts_customizable.lua | 16 ++ components/widget/html/widget_html_all.lua | 2 + components/widget/html/widget_html_li.lua | 27 +++ components/widget/html/widget_html_ul.lua | 27 +++ .../infobox/widget_infobox_breakdown.lua | 58 +++---- .../widget/infobox/widget_infobox_cell.lua | 98 ++++------- .../widget/infobox/widget_infobox_center.lua | 38 ++--- .../infobox/widget_infobox_chronology.lua | 128 +++++++------- .../widget/infobox/widget_infobox_core.lua | 13 +- .../widget/infobox/widget_infobox_header.lua | 161 +++++++----------- .../infobox/widget_infobox_highlights.lua | 30 ++-- .../widget/infobox/widget_infobox_links.lua | 73 ++++---- .../widget/infobox/widget_infobox_title.lua | 24 +-- .../misc/widget_misc_inline_icon_and_text.lua | 23 ++- components/widget/widget.lua | 81 ++------- components/widget/widget_builder.lua | 19 +-- components/widget/widget_customizable.lua | 15 +- components/widget/widget_table_old.lua | 2 +- components/widget/widget_table_row.lua | 2 +- spec/array_spec.lua | 6 + standard/array.lua | 16 ++ 23 files changed, 408 insertions(+), 471 deletions(-) create mode 100644 components/widget/contexts/widget_contexts_customizable.lua create mode 100644 components/widget/html/widget_html_li.lua create mode 100644 components/widget/html/widget_html_ul.lua diff --git a/components/infobox/commons/infobox_basic.lua b/components/infobox/commons/infobox_basic.lua index 64fd42a6974..b841376f669 100644 --- a/components/infobox/commons/infobox_basic.lua +++ b/components/infobox/commons/infobox_basic.lua @@ -102,13 +102,19 @@ end ---@param widgets Widget[] ---@return string function BasicInfobox:build(widgets) - return Infobox{ + local infobox = Infobox{ gameName = self.wiki, forceDarkMode = Logic.readBool(self.args.darkmodeforced), bottomContent = self.bottomContent, warnings = self.warnings, children = widgets, - }:tryMake(self.injector) or '' + } + if self.injector then + -- Customizable backwards compatibility + local CustomizableContext = Lua.import('Module:Widget/Contexts/Customizable') + return CustomizableContext.LegacyCustomizable{value = self.injector, children = {infobox}}:tryMake() + end + return infobox:tryMake() end return BasicInfobox diff --git a/components/prize_pool/commons/prize_pool_base.lua b/components/prize_pool/commons/prize_pool_base.lua index fad19e5c5c9..92a55245cda 100644 --- a/components/prize_pool/commons/prize_pool_base.lua +++ b/components/prize_pool/commons/prize_pool_base.lua @@ -644,11 +644,11 @@ function BasePrizePool:_buildRows() local lastCellOfType = previousOfPrizeType[prize.type] if lastCellOfType and prizeTypeData.mergeDisplayColumns then - if Table.isNotEmpty(lastCellOfType.children) and Table.isNotEmpty(cell.children) then - table.insert(lastCellOfType.children, tostring(mw.html.create('hr'):css('width', '100%'))) + if Table.isNotEmpty(lastCellOfType.props.children) and Table.isNotEmpty(cell.props.children) then + table.insert(lastCellOfType.props.children, tostring(mw.html.create('hr'):css('width', '100%'))) end - Array.extendWith(lastCellOfType.children, cell.children) + Array.extendWith(lastCellOfType.props.children, cell.props.children) lastCellOfType.css['flex-direction'] = 'column' return nil @@ -662,11 +662,11 @@ function BasePrizePool:_buildRows() local lastInColumn = previousOpponent[columnIndex] ---@cast prizeCell -nil - if Table.isEmpty(prizeCell.children) then + if Table.isEmpty(prizeCell.props.children) then prizeCell = BasePrizePool._emptyCell() end - if lastInColumn and Table.deepEquals(lastInColumn.children, prizeCell.children) then + if lastInColumn and Table.deepEquals(lastInColumn.props.children, prizeCell.props.children) then lastInColumn.rowSpan = (lastInColumn.rowSpan or 1) + 1 else previousOpponent[columnIndex] = prizeCell diff --git a/components/widget/contexts/widget_contexts_customizable.lua b/components/widget/contexts/widget_contexts_customizable.lua new file mode 100644 index 00000000000..7ec76347293 --- /dev/null +++ b/components/widget/contexts/widget_contexts_customizable.lua @@ -0,0 +1,16 @@ +--- +-- @Liquipedia +-- wiki=commons +-- page=Module:Widget/Contexts/Customizable +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Class = require('Module:Class') +local Lua = require('Module:Lua') +local Context = Lua.import('Module:Widget/Context') + +-- Customizable backwards compatibility +return { + LegacyCustomizable = Class.new(Context), +} diff --git a/components/widget/html/widget_html_all.lua b/components/widget/html/widget_html_all.lua index ed1ec7c9f69..69cc26dc998 100644 --- a/components/widget/html/widget_html_all.lua +++ b/components/widget/html/widget_html_all.lua @@ -12,10 +12,12 @@ local Lua = require('Module:Lua') Widgets.Div = Lua.import('Module:Widget/Html/Div') Widgets.Fragment = Lua.import('Module:Widget/Html/Fragment') +Widgets.Li = Lua.import('Module:Widget/Html/Li') Widgets.Span = Lua.import('Module:Widget/Html/Span') Widgets.Table = Lua.import('Module:Widget/Html/Table') Widgets.Td = Lua.import('Module:Widget/Html/Td') Widgets.Th = Lua.import('Module:Widget/Html/Th') Widgets.Tr = Lua.import('Module:Widget/Html/Tr') +Widgets.Ul = Lua.import('Module:Widget/Html/Ul') return Widgets diff --git a/components/widget/html/widget_html_li.lua b/components/widget/html/widget_html_li.lua new file mode 100644 index 00000000000..66ce0efdabf --- /dev/null +++ b/components/widget/html/widget_html_li.lua @@ -0,0 +1,27 @@ +--- +-- @Liquipedia +-- wiki=commons +-- page=Module:Widget/Html/Li +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Class = require('Module:Class') +local Lua = require('Module:Lua') +local Table = require('Module:Table') + +local WidgetHtml = Lua.import('Module:Widget/Html/Base') + +---@class WidgetLi: WidgetHtmlBase +---@operator call(table): WidgetLi +local Li = Class.new(WidgetHtml) + +---@return Html +function Li:render() + local attributes = Table.copy(self.props.attributes or {}) + attributes.class = self.props.classes + attributes.style = self.props.css + return self:renderAs('li', self.props.children, attributes) +end + +return Li diff --git a/components/widget/html/widget_html_ul.lua b/components/widget/html/widget_html_ul.lua new file mode 100644 index 00000000000..ae20b95a556 --- /dev/null +++ b/components/widget/html/widget_html_ul.lua @@ -0,0 +1,27 @@ +--- +-- @Liquipedia +-- wiki=commons +-- page=Module:Widget/Html/Ul +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Class = require('Module:Class') +local Lua = require('Module:Lua') +local Table = require('Module:Table') + +local WidgetHtml = Lua.import('Module:Widget/Html/Base') + +---@class WidgetUl: WidgetHtmlBase +---@operator call(table): WidgetUl +local Ul = Class.new(WidgetHtml) + +---@return Html +function Ul:render() + local attributes = Table.copy(self.props.attributes or {}) + attributes.class = self.props.classes + attributes.style = self.props.css + return self:renderAs('ul', self.props.children, attributes) +end + +return Ul diff --git a/components/widget/infobox/widget_infobox_breakdown.lua b/components/widget/infobox/widget_infobox_breakdown.lua index cbaf0cce219..eacfc0a9089 100644 --- a/components/widget/infobox/widget_infobox_breakdown.lua +++ b/components/widget/infobox/widget_infobox_breakdown.lua @@ -6,53 +6,41 @@ -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- +local Array = require('Module:Array') local Class = require('Module:Class') local Lua = require('Module:Lua') +local Table = require('Module:Table') local Widget = Lua.import('Module:Widget') +local WidgetUtil = Lua.import('Module:Widget/Util') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') ---@class BreakdownWidget: Widget ----@operator call({children:(string|number)[],classes:string[],contentClasses:table}):BreakdownWidget +---@operator call(table):BreakdownWidget ---@field classes string[] ---@field contentClasses table --can have gaps in the outer table -local Breakdown = Class.new( - Widget, - function(self, input) - self.classes = input.classes - self.contentClasses = input.contentClasses or {} - end -) - ----@param children string[] ----@return string? -function Breakdown:make(children) - return Breakdown:_breakdown(children, self.classes, self.contentClasses) -end +local Breakdown = Class.new(Widget) ----@param contents (string|number)[] ----@param classes string[] ----@param contentClasses table --can have gaps in the outer table ----@return string? -function Breakdown:_breakdown(contents, classes, contentClasses) - if type(contents) ~= 'table' or contents == {} then +---@return Widget? +function Breakdown:render() + if Table.isEmpty(self.props.children) then return nil end - local div = mw.html.create('div') - local number = #contents - for contentIndex, content in ipairs(contents) do - local infoboxCustomCell = mw.html.create('div'):addClass('infobox-cell-' .. number) - for _, class in pairs(classes or {}) do - infoboxCustomCell:addClass(class) - end - for _, class in pairs(contentClasses['content' .. contentIndex] or {}) do - infoboxCustomCell:addClass(class) - end - infoboxCustomCell:wikitext(content) - div:node(infoboxCustomCell) - end - - return tostring(div) + local number = #self.props.children + local mappedChildren = Array.map(self.props.children, function(child, childIndex) + return HtmlWidgets.Div{ + children = {child}, + classes = WidgetUtil.collect( + 'infobox-cell-' .. number, + self.props.classes, + (self.props.contentClasses or {})['content' .. childIndex] + ), + } + end) + return HtmlWidgets.Div{ + children = mappedChildren, + } end return Breakdown diff --git a/components/widget/infobox/widget_infobox_cell.lua b/components/widget/infobox/widget_infobox_cell.lua index a11c5f84041..b0e9291efca 100644 --- a/components/widget/infobox/widget_infobox_cell.lua +++ b/components/widget/infobox/widget_infobox_cell.lua @@ -8,8 +8,11 @@ local Class = require('Module:Class') local Lua = require('Module:Lua') +local Logic = require('Module:Logic') local Widget = Lua.import('Module:Widget') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Link = Lua.import('Module:Widget/Basic/Link') ---@class CellWidgetOptions ---@field columns number? @@ -17,91 +20,52 @@ local Widget = Lua.import('Module:Widget') ---@field surpressColon boolean? ---@class CellWidget: Widget ----@operator call({name:string|number,content:(string|number)[],classes:string[]?,options:CellWidgetOptions}):CellWidget ----@field name string|number ----@field options CellWidgetOptions ----@field classes string[]? +---@operator call(table):CellWidget local Cell = Class.new(Widget, function(self, input) self.name = self:assertExistsAndCopy(input.name) - self.children = input.children or input.content or {} - self.options = input.options or {} - self.classes = input.classes - - self.options.columns = self.options.columns or 2 + self.props.children = input.children or input.content or {} end ) ----@param description string|number ----@return CellWidget -function Cell:_new(description) - self.root = mw.html.create('div') - self.description = mw.html.create('div') - self.description:addClass('infobox-cell-'.. self.options.columns) - :addClass('infobox-description') - :wikitext(description) - :wikitext(not self.options.surpressColon and ':' or nil) - self.contentDiv = nil - return self -end - ----@param ... string ----@return CellWidget -function Cell:_class(...) - for i = 1, select('#', ...) do - local item = select(i, ...) - if item == nil then - break - end - - self.root:addClass(item) +---@return Widget? +function Cell:render() + if Logic.isEmpty(self.props.children) then + return end - return self -end ----@param ... string ----@return CellWidget -function Cell:_content(...) - local firstItem = select(1, ...) - if firstItem == nil or firstItem == '' then - self.contentDiv = nil - return self - end + local options = self.props.options or {} + options.columns = options.columns or 2 - self.contentDiv = mw.html.create('div') - self.contentDiv:css('width', (100 * (self.options.columns - 1) / self.options.columns) .. '%') -- 66.66% for col = 3 - for i = 1, select('#', ...) do + local mappedChildren = {} + for i, child in ipairs(self.props.children) do if i > 1 then - self.contentDiv:wikitext('
') - end - local item = select(i, ...) ---@type string? - if item == nil then - break + table.insert(mappedChildren, '
') end - - if self.options.makeLink == true then - self.contentDiv:wikitext('[[' .. item .. ']]') + if options.makeLink then + table.insert(mappedChildren, Link{children = {child}, link = child}) else - self.contentDiv:wikitext(item) + table.insert(mappedChildren, child) end end - return self -end - ----@param children string[] ----@return string? -function Cell:make(children) - self:_new(self.name) - self:_class(unpack(self.classes or {})) - self:_content(unpack(children)) - if self.contentDiv == nil then + if Logic.isEmpty(mappedChildren[1]) then return end - self.root :node(self.description) - :node(self.contentDiv) - return tostring(self.root) + return HtmlWidgets.Div{ + classes = self.props.classes, + children = { + HtmlWidgets.Div{ + classes = {'infobox-cell-' .. options.columns, 'infobox-description'}, + children = {self.props.name, not options.surpressColon and ':' or nil} + }, + HtmlWidgets.Div{ + css = {width = (100 * (options.columns - 1) / options.columns) .. '%'}, -- 66.66% for col = 3 + children = mappedChildren, + } + } + } end return Cell diff --git a/components/widget/infobox/widget_infobox_center.lua b/components/widget/infobox/widget_infobox_center.lua index 6fd73ddfbc3..10b36748916 100644 --- a/components/widget/infobox/widget_infobox_center.lua +++ b/components/widget/infobox/widget_infobox_center.lua @@ -11,41 +11,23 @@ local Lua = require('Module:Lua') local Table = require('Module:Table') local Widget = Lua.import('Module:Widget') +local WidgetUtil = Lua.import('Module:Widget/Util') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') ---@class CentereWidget: Widget ---@operator call(table): CentereWidget ---@field classes string[] -local Center = Class.new( - Widget, - function(self, input) - self.classes = input.classes - end -) - ----@param children string[] ----@return string? -function Center:make(children) - return Center:_create(children, self.classes) -end +local Center = Class.new(Widget) ----@param content (string|number)[] ----@param classes string[] ----@return string? -function Center:_create(content, classes) - if Table.isEmpty(content) then +---@return Widget? +function Center:render() + if Table.isEmpty(self.props.children) then return nil end - - local centered = mw.html.create('div'):addClass('infobox-center') - for _, class in ipairs(classes or {}) do - centered:addClass(class) - end - - for _, item in pairs(content) do - centered:wikitext(item) - end - - return tostring(mw.html.create('div'):node(centered)) + return HtmlWidgets.Div{children = {HtmlWidgets.Div{ + classes = WidgetUtil.collect('infobox-center', self.props.classes), + children = self.props.children + }}} end return Center diff --git a/components/widget/infobox/widget_infobox_chronology.lua b/components/widget/infobox/widget_infobox_chronology.lua index 32f2149af99..dfcd20fc1cd 100644 --- a/components/widget/infobox/widget_infobox_chronology.lua +++ b/components/widget/infobox/widget_infobox_chronology.lua @@ -6,96 +6,102 @@ -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- +local Array = require('Module:Array') local Class = require('Module:Class') +local Logic = require('Module:Logic') local Lua = require('Module:Lua') local Table = require('Module:Table') local Widget = Lua.import('Module:Widget') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Div = HtmlWidgets.Div +local InlineIconAndText = Lua.import('Module:Widget/Misc/InlineIconAndText') +local IconFa = Lua.import('Module:Widget/Image/Icon/Fontawesome') ---@class ChronologyWidget: Widget ----@operator call({links: table}): ChronologyWidget +---@operator call(table): ChronologyWidget ---@field links table -local Chronology = Class.new( - Widget, - function(self, input) - self.links = input.links - end -) - ----@param children string[] ----@return string? -function Chronology:make(children) - return Chronology:_chronology(self.links) -end +local Chronology = Class.new(Widget) ----@param links table ---@return string? -function Chronology:_chronology(links) - if links == nil or Table.size(links) == 0 then +function Chronology:render() + if Table.isEmpty(self.props.links) then return end - local chronologyContent = mw.html.create() - chronologyContent:node(self:_createChronologyRow(links['previous'], links['next'])) - - local index = 2 - local previous = links['previous' .. index] - local next = links['next' .. index] - while (previous ~= nil or next ~= nil) do - chronologyContent:node(self:_createChronologyRow(previous, next)) - - index = index + 1 - previous = links['previous' .. index] - next = links['next' .. index] - end - - return tostring(chronologyContent) + return HtmlWidgets.Fragment{ + children = Array.mapIndexes(function(index) + local prevKey, nextKey = 'previous' .. index, 'next' .. index + if index == 1 then + prevKey, nextKey = 'previous', 'next' + end + return self:_createChronologyRow(self.props.links[prevKey], self.props.links[nextKey]) + end) + } end ---@param previous string|number|nil ---@param next string|number|nil ---@return Html? function Chronology:_createChronologyRow(previous, next) - local doesPreviousExist = previous ~= nil and previous ~= '' - local doesNextExist = next ~= nil and next ~= '' + local doesPreviousExist = Logic.isNotEmpty(previous) + local doesNextExist = Logic.isNotEmpty(next) if not doesPreviousExist and not doesNextExist then return nil end - local node = mw.html.create('div') - - local previousWrapper = mw.html.create('div'):addClass('infobox-cell-2') - if doesPreviousExist then - previousWrapper :addClass('infobox-text-left') - - local previousArrow = mw.html.create('div') - previousArrow :addClass('infobox-arrow-icon') - :css('float', 'left') - :wikitext('[[File:Arrow sans left.svg|link=' .. previous .. ']]') - - previousWrapper :node(previousArrow) - :wikitext(' [[' .. previous .. ']]') + local function splitInputIntoLinkAndText(input) + return unpack(mw.text.split(input, '|')) end - node:node(previousWrapper) - if doesNextExist then - local nextWrapper = mw.html.create('div') - nextWrapper :addClass('infobox-cell-2') - :addClass('infobox-text-right') - - local nextArrow = mw.html.create('div') - nextArrow :addClass('infobox-arrow-icon') - :css('float', 'right') - :wikitext('[[File:Arrow sans right.svg|link=' .. next .. ']]') - - nextWrapper :wikitext('[[' .. next .. ']] ') - :node(nextArrow) + local function nextSlot() + if not doesNextExist then + return + end + local link, text = splitInputIntoLinkAndText(next) + return Div{ + classes = {'infobox-cell-2', 'infobox-text-right'}, + children = { + InlineIconAndText{ + link = link, + text = text, + icon = IconFa{ + iconName = 'next', + link = link, + }, + } + } + } + end - node:node(nextWrapper) + local function prevSlot() + if not doesPreviousExist then + return + end + local link, text = splitInputIntoLinkAndText(previous) + return Div{ + classes = {'infobox-cell-2', 'infobox-text-left'}, + children = { + InlineIconAndText{ + link = link, + text = text, + icon = IconFa{ + iconName = 'previous', + link = link, + }, + options = {flipped = true}, + } + } + } end - return node + return Div{ + children = { + prevSlot() or Div{classes = 'infobox-cell-2'}, + nextSlot(), + } + } end return Chronology diff --git a/components/widget/infobox/widget_infobox_core.lua b/components/widget/infobox/widget_infobox_core.lua index c2c910b54fb..4ab3734d46c 100644 --- a/components/widget/infobox/widget_infobox_core.lua +++ b/components/widget/infobox/widget_infobox_core.lua @@ -19,21 +19,18 @@ local Fragment = HtmlWidgets.Fragment ---@class Infobox: Widget ---@operator call(table): Infobox ---@field props table -local Infobox = Class.new(Widget, function(self, props) - self.props = props -end) +local Infobox = Class.new(Widget) ----@param children string[] ---@return string -function Infobox:make(children) +function Infobox:render() local firstInfobox = not Variables.varDefault('has_infobox') Variables.varDefine('has_infobox', 'true') local adbox = Div{classes = {'fo-nttax-infobox-adbox'}, children = {mw.getCurrentFrame():preprocess('')}} - local content = Div{classes = {'fo-nttax-infobox'}, children = children} + local content = Div{classes = {'fo-nttax-infobox'}, children = self.props.children} local bottomContent = Div{children = self.props.bottomContent} - return tostring(Fragment{children = { + return Fragment{children = { Div{ classes = { 'fo-nttax-infobox-wrapper', @@ -47,7 +44,7 @@ function Infobox:make(children) } }, WarningBox.displayAll(self.props.warnings), - }}) + }} end return Infobox diff --git a/components/widget/infobox/widget_infobox_header.lua b/components/widget/infobox/widget_infobox_header.lua index b293096bac4..3a62d29e59e 100644 --- a/components/widget/infobox/widget_infobox_header.lua +++ b/components/widget/infobox/widget_infobox_header.lua @@ -7,89 +7,63 @@ -- local Class = require('Module:Class') +local Logic = require('Module:Logic') local Lua = require('Module:Lua') +local WidgetUtil = Lua.import('Module:Widget/Util') local Widget = Lua.import('Module:Widget') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Div = HtmlWidgets.Div ---@class HeaderWidget: Widget ---@operator call(table): HeaderWidget ----@field name string? ----@field subHeader string? ----@field image string? ----@field imageDefault string? ----@field imageDark string? ----@field imageDefaultDark string? ----@field size number|string|nil ----@field imageText string? -local Header = Class.new( - Widget, - function(self, input) - self.name = input.name - self.subHeader = input.subHeader - self.image = input.image - self.imageDefault = input.imageDefault - self.imageDark = input.imageDark - self.imageDefaultDark = input.imageDefaultDark - self.size = input.size - self.imageText = input.imageText - end -) - ----@param children string[] ----@return string -function Header:make(children) - local header = { - Header:_name(self.name), - Header:_image( - self.image, - self.imageDark, - self.imageDefault, - self.imageDefaultDark, - self.size, - self.imageText - ) - } - - if self.image then - mw.ext.SearchEngineOptimization.metaimage(self.image) - end +local Header = Class.new(Widget) - local subHeader = Header:_subHeader(self.subHeader) - if subHeader then - table.insert(header, 2, subHeader) +function Header:render() + if self.props.image then + mw.ext.SearchEngineOptimization.metaimage(self.props.image) end - local wrapper = mw.html.create() - for _, element in ipairs(header) do - wrapper:node(element) - end - return tostring(wrapper) + return HtmlWidgets.Fragment{ + children = WidgetUtil.collect( + self:_name(), + self:_subHeader(), + self:_image( + self.props.image, + self.props.imageDark, + self.props.imageDefault, + self.props.imageDefaultDark, + self.props.size, + self.props.imageText + ) + ) + } end ----@param name string? ----@return Html -function Header:_name(name) - local pagename = name or mw.title.getCurrentTitle().text - local infoboxHeader = mw.html.create('div') - infoboxHeader :addClass('infobox-header') - :addClass('wiki-backgroundcolor-light') - :node(self:_createInfoboxButtons()) - :wikitext(pagename) - return mw.html.create('div'):node(infoboxHeader) +---@return Widget +function Header:_name() + return Div{children = {Div{ + classes = {'infobox-header', 'wiki-backgroundcolor-light'}, + children = { + self:_createInfoboxButtons(), + self.props.name or mw.title.getCurrentTitle().text, + } + }}} end ----@param subHeader string? ----@return Html? -function Header:_subHeader(subHeader) - if not subHeader then +---@return Widget? +function Header:_subHeader() + if not self.props.subHeader then return nil end - local infoboxSubHeader = mw.html.create('div') - infoboxSubHeader:addClass('infobox-header') - :addClass('wiki-backgroundcolor-light') - :addClass('infobox-header-2') - :wikitext(subHeader) - return mw.html.create('div'):node(infoboxSubHeader) + return Div{ + children = { + Div{ + classes = {'infobox-header', 'wiki-backgroundcolor-light', 'infobox-header-2'}, + children = {self.props.subHeader} + } + } + } end ---@param fileName string? @@ -100,59 +74,58 @@ end ---@param imageText string? ---@return Html? function Header:_image(fileName, fileNameDark, default, defaultDark, size, imageText) - if (fileName == nil or fileName == '') and (default == nil or default == '') then + if Logic.isEmpty(fileName) and Logic.isEmpty(default) then return nil end local imageName = fileName or default ---@cast imageName -nil - local infoboxImage = Header:_makeSizedImage(imageName, fileName, size, 'lightmode') + local infoboxImage = Header:_makeSizedImage(imageName, size, 'lightmode') imageName = fileNameDark or fileName or defaultDark or default ---@cast imageName -nil - local infoboxImageDark = Header:_makeSizedImage(imageName, fileNameDark or fileName, size, 'darkmode') + local infoboxImageDark = Header:_makeSizedImage(imageName, size, 'darkmode') local imageTextNode = Header:_makeImageText(imageText) - return mw.html.create('div'):addClass('infobox-image-wrapper') - :node(infoboxImage) - :node(infoboxImageDark) - :node(imageTextNode) + return Div{ + classes = {'infobox-image-wrapper'}, + children = {infoboxImage, infoboxImageDark, imageTextNode}, + } end ---@param imageName string ----@param fileName string? ---@param size number|string|nil ---@param mode string ---@return Html -function Header:_makeSizedImage(imageName, fileName, size, mode) - local infoboxImage = mw.html.create('div'):addClass('infobox-image ' .. mode) +function Header:_makeSizedImage(imageName, size, mode) + local fixedSize = false -- Number (interpret as pixels) size = size or '' if tonumber(size) then size = tonumber(size) .. 'px' - infoboxImage:addClass('infobox-fixed-size-image') + fixedSize = true -- Percentage (interpret as scaling) elseif size:find('%%') then local scale = size:gsub('%%', '') local scaleNumber = tonumber(scale) if scaleNumber then size = 'frameless|upright=' .. (scaleNumber / 100) - infoboxImage:addClass('infobox-fixed-size-image') + fixedSize = true end -- Default else size = '600px' end - local fullFileName = '[[File:' .. imageName .. '|center|' .. size .. ']]' - infoboxImage:wikitext(fullFileName) - - return infoboxImage + return Div{ + classes = {'infobox-image ' .. mode, fixedSize and 'infobox-fixed-size-image' or nil}, + children = {'[[File:' .. imageName .. '|center|' .. size .. ']]'}, + } end ----@return Html +---@return Widget function Header:_createInfoboxButtons() local rootFrame local currentFrame = mw.getCurrentFrame() @@ -163,34 +136,32 @@ function Header:_createInfoboxButtons() local moduleTitle = rootFrame:getTitle() - local buttons = mw.html.create('span') - buttons:addClass('infobox-buttons') - buttons:addClass('navigation-not-searchable') - -- Quick edit link - buttons:node( + local editLink = mw.text.nowiki('[') .. '[' .. mw.site.server .. tostring(mw.uri.localUrl( mw.title.getCurrentTitle().prefixedText, 'action=edit§ion=0' )) .. ' e]' .. mw.text.nowiki(']') - ) -- Quick help link (links to template) if not mw.title.new(moduleTitle).exists then moduleTitle = 'lpcommons:'.. moduleTitle end - buttons:node(mw.text.nowiki('[') .. '[[' .. moduleTitle ..'|h]]' .. mw.text.nowiki(']')) + local helpLink = mw.text.nowiki('[') .. '[[' .. moduleTitle .. '|h]]' .. mw.text.nowiki(']') - return buttons + return HtmlWidgets.Span{ + classes = {'infobox-buttons', 'navigation-not-searchable'}, + children = {editLink, helpLink} + } end ---@param text string? ----@return Html? +---@return Widget? function Header:_makeImageText(text) if not text then return end - return mw.html.create('div'):addClass('infobox-image-text'):wikitext(text) + return Div{classes = 'infobox-image-text', children = {text}} end return Header diff --git a/components/widget/infobox/widget_infobox_highlights.lua b/components/widget/infobox/widget_infobox_highlights.lua index 2dba456abda..25c3ddd4299 100644 --- a/components/widget/infobox/widget_infobox_highlights.lua +++ b/components/widget/infobox/widget_infobox_highlights.lua @@ -6,39 +6,29 @@ -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- +local Array = require('Module:Array') local Class = require('Module:Class') local Lua = require('Module:Lua') local Table = require('Module:Table') local Widget = Lua.import('Module:Widget') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') ---@class HighlightsWidget: Widget ---@operator call(table):HighlightsWidget local Highlights = Class.new(Widget) ----@param children string[] ---@return string? -function Highlights:make(children) - return Highlights:_highlights(children) -end - ----@param list (string|number)[]? ----@return string? -function Highlights:_highlights(list) - if list == nil or Table.size(list) == 0 then +function Highlights:render() + if Table.isEmpty(self.props.children) then return nil end - - local div = mw.html.create('div') - local highlights = mw.html.create('ul') - - for _, item in ipairs(list) do - highlights:tag('li'):wikitext(item):done() - end - - div:node(highlights) - - return tostring(div) + local listItems = Array.map(self.props.children, function(child) + return HtmlWidgets.Li{children = {child}} + end) + return HtmlWidgets.Div{ + children = {HtmlWidgets.Ul{children = listItems}}, + } end return Highlights diff --git a/components/widget/infobox/widget_infobox_links.lua b/components/widget/infobox/widget_infobox_links.lua index 8d863b8be8a..08a589ee19e 100644 --- a/components/widget/infobox/widget_infobox_links.lua +++ b/components/widget/infobox/widget_infobox_links.lua @@ -6,57 +6,59 @@ -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- +local Array = require('Module:Array') local Class = require('Module:Class') local Lua = require('Module:Lua') local Table = require('Module:Table') local UtilLinks = Lua.import('Module:Links') local Widget = Lua.import('Module:Widget') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Link = Lua.import('Module:Widget/Basic/Link') ---@class LinksWidget: Widget ----@operator call({links: table, variant: string?}): LinksWidget ----@field links table ----@field variant string? -local Links = Class.new( - Widget, - function(self, input) - self.links = Table.copy(input.links) - self.variant = input.variant - end -) +---@operator call(table): LinksWidget +local Links = Class.new(Widget) local PRIORITY_GROUPS = Lua.import('Module:Links/PriorityGroups', {loadData = true}) ----@param children string[] ----@return string? -function Links:make(children) - local infoboxLinks = mw.html.create('div') - infoboxLinks :addClass('infobox-center') - :addClass('infobox-icons') +---@return Widget? +function Links:render() + if Table.isEmpty(self.props.links) then + return nil + end + local linkInputs = Table.copy(self.props.links) + + local links = {} for _, group in Table.iter.spairs(PRIORITY_GROUPS) do - for _, key in ipairs(group) do - if self.links[key] ~= nil then - infoboxLinks:wikitext(' ' .. self:_makeLink(key, self.links[key])) - -- Remove link from the collection - self.links[key] = nil + Array.forEach(group, function(key) + if not linkInputs[key] then + return + end + + table.insert(links, self:_makeLink(key, linkInputs[key])) + -- Remove link from the collection + linkInputs[key] = nil - local index = 2 - while self.links[key .. index] ~= nil do - infoboxLinks:wikitext(' ' .. self:_makeLink(key, self.links[key .. index])) - -- Remove link from the collection - self.links[key .. index] = nil - index = index + 1 - end + local index = 2 + while linkInputs[key .. index] ~= nil do + table.insert(self:_makeLink(key, linkInputs[key .. index])) + -- Remove link from the collection + linkInputs[key .. index] = nil + index = index + 1 end - end + end) end - for key, value in Table.iter.spairs(self.links) do - infoboxLinks:wikitext(' ' .. self:_makeLink(key, value)) + for key, value in Table.iter.spairs(linkInputs) do + table.insert(self:_makeLink(key, value)) end - return tostring(mw.html.create('div'):node(infoboxLinks)) + return HtmlWidgets.Div{children = HtmlWidgets.Div{ + classes = {'infobox-center', 'infobox-icons'}, + children = Array.interleave(links, ' ') + }} end ---@param key string @@ -64,8 +66,11 @@ end ---@return string function Links:_makeLink(key, value) key = UtilLinks.removeAppendedNumber(key) - return '[' .. UtilLinks.makeFullLink(key, value, self.variant) .. - ' ' .. UtilLinks.makeIcon(key) .. ']' + return Link{ + linktype = 'external', + link = UtilLinks.makeFullLink(key, value, self.props.variant), + children = {UtilLinks.makeIcon(key)}, + } end return Links diff --git a/components/widget/infobox/widget_infobox_title.lua b/components/widget/infobox/widget_infobox_title.lua index 4d59d262dc8..bb937b6957a 100644 --- a/components/widget/infobox/widget_infobox_title.lua +++ b/components/widget/infobox/widget_infobox_title.lua @@ -10,6 +10,7 @@ local Class = require('Module:Class') local Lua = require('Module:Lua') local Widget = Lua.import('Module:Widget') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') ---@class TitleWidget: Widget ---@operator call(table): TitleWidget @@ -18,27 +19,18 @@ local Title = Class.new( function(self) -- Legacy support for single string children, convert to array -- Widget v2.1 will have this support added to the base class - if type(self.children) == 'string' then - self.children = {self.children} + if type(self.props.children) == 'string' then + self.props.children = {self.props.children} end end ) ----@param children string[] ---@return string? -function Title:make(children) - return Title:_create(table.concat(children)) -end - ----@param infoDescription string|number|nil ----@return string -function Title:_create(infoDescription) - local header = mw.html.create('div') - header :addClass('infobox-header') - :addClass('wiki-backgroundcolor-light') - :addClass('infobox-header-2') - :wikitext(infoDescription) - return tostring(mw.html.create('div'):node(header)) +function Title:render() + return HtmlWidgets.Div{children = {HtmlWidgets.Div{ + children = self.props.children, + classes = {'infobox-header', 'wiki-backgroundcolor-light', 'infobox-header-2'} + }}} end return Title diff --git a/components/widget/misc/widget_misc_inline_icon_and_text.lua b/components/widget/misc/widget_misc_inline_icon_and_text.lua index 4958ffc2e7c..a0803cec168 100644 --- a/components/widget/misc/widget_misc_inline_icon_and_text.lua +++ b/components/widget/misc/widget_misc_inline_icon_and_text.lua @@ -6,6 +6,7 @@ -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- +local Array = require('Module:Array') local Class = require('Module:Class') local Lua = require('Module:Lua') @@ -27,17 +28,21 @@ local InlineIconAndText = Class.new(Widget) ---@return Widget function InlineIconAndText:render() + local children = { + Link{ + link = self.props.link, + linktype = 'internal', + children = {self.props.text} + }, + ' ', + self.props.icon, + } + if self.props.options and self.props.options.flipped then + children = Array.reverse(children) + end return Span{ classes = {'image-link'}, - children = { - self.props.icon, - ' ', - Link{ - link = self.props.link, - linktype = 'internal', - children = {self.props.text} - } - }, + children = children, } end diff --git a/components/widget/widget.lua b/components/widget/widget.lua index ab7afa3eb60..df343a241a9 100644 --- a/components/widget/widget.lua +++ b/components/widget/widget.lua @@ -15,16 +15,13 @@ local Table = require('Module:Table') ---@class Widget: BaseClass ---@operator call(table): self ----@field children (Widget|Html|string|number)[] @deprecated ---@field context Widget[] ---@field props table ----@field makeChildren? fun(self:Widget, injector: WidgetInjector?): Widget[]? +---@field injector WidgetInjector? local Widget = Class.new(function(self, props) self.props = Table.copy(props) or {} self.props.children = self.props.children or {} self.context = {} -- Populated by the parent - ---@deprecated Widget v1 backwards compability - self.children = self.children or self.props.children end) ---Asserts the existence of a value and copies it @@ -39,46 +36,26 @@ function Widget:render() error('A Widget must override the render() function!') end ----@deprecated ----@param children string[] ----@return string|nil -function Widget:make(children) - error('A Widget must override the make() function!') -end - ----@param injector WidgetInjector? ---@return string -function Widget:tryMake(injector) - local renderComponent - if self.render == Widget.render then - -- Widget v1 backwards compability - renderComponent = function() - local processedChildren = self:tryChildren(injector) - if self.render == Widget.render then - local ret = self:make(processedChildren) - return ret ~= nil and ret or '' - end +function Widget:tryMake() + local function renderComponent() + local ret = self:render() + if not Array.isArray(ret) then + ret = {ret} end - else - renderComponent = function() - local ret = self:render() - if not Array.isArray(ret) then - ret = {ret} - end + ---@cast ret (string|Widget|Html|nil)[] - ---@cast ret (string|Widget|Html|nil)[] - return Array.reduce(ret, function(acc, val) - if Class.instanceOf(val, Widget) then - ---@cast val Widget - val.context = self:_nextContext() - return acc .. val:tryMake(injector) - end - if val ~= nil then - return acc .. tostring(val) - end - return acc - end, '') - end + return table.concat(Array.map(ret, function(val) + if Class.instanceOf(val, Widget) then + ---@cast val Widget + val.context = self:_nextContext() + return val:tryMake() + end + if val ~= nil then + return tostring(val) + end + return nil + end)) end return Logic.tryOrElseLog( @@ -88,28 +65,6 @@ function Widget:tryMake(injector) ) end ----@deprecated ----@param injector WidgetInjector? ----@return string[] -function Widget:tryChildren(injector) - local children = self.children - if self.makeChildren then - children = self:makeChildren(injector) or {} - end - return Array.map(children, function(child) - if Class.instanceOf(child, Widget) then - ---@cast child Widget - return Logic.tryOrElseLog( - function() return child:tryMake(injector) end, - FnUtil.curry(self.getDerivedStateFromError, self), - Widget._updateErrorHeader - ) - end - ---@cast child -Widget - return tostring(child) - end) -end - ---@param widget WidgetContext ---@param default any ---@return any diff --git a/components/widget/widget_builder.lua b/components/widget/widget_builder.lua index 09637f7715a..8d974c01d13 100644 --- a/components/widget/widget_builder.lua +++ b/components/widget/widget_builder.lua @@ -13,24 +13,11 @@ local Widget = Lua.import('Module:Widget') ---@class BuilderWidget: Widget ---@operator call({builder: function}): BuilderWidget ----@field builder fun(): Widget[] -local Builder = Class.new( - Widget, - function(self, input) - self.builder = input.builder - end -) +local Builder = Class.new(Widget) ----@param children string[] ----@return string -function Builder:make(children) - return table.concat(children) -end - ----@param injector WidgetInjector? ---@return Widget[]? -function Builder:makeChildren(injector) - return self.builder() +function Builder:render() + return self.props.builder() end return Builder diff --git a/components/widget/widget_customizable.lua b/components/widget/widget_customizable.lua index 817a6559b30..8d962cec84c 100644 --- a/components/widget/widget_customizable.lua +++ b/components/widget/widget_customizable.lua @@ -10,6 +10,7 @@ local Class = require('Module:Class') local Lua = require('Module:Lua') local Widget = Lua.import('Module:Widget') +local CustomizableContext = Lua.import('Module:Widget/Contexts/Customizable') ---@class CustomizableWidget: Widget ---@operator call({id: string, children: Widget[]}): CustomizableWidget @@ -21,19 +22,13 @@ local Customizable = Class.new( end ) ----@param children string[] ----@return string -function Customizable:make(children) - return table.concat(children) -end - ----@param injector WidgetInjector? ---@return Widget[]? -function Customizable:makeChildren(injector) +function Customizable:render() + local injector = self:useContext(CustomizableContext.LegacyCustomizable) if injector == nil then - return self.children + return self.props.children end - return injector:parse(self.id, self.children) + return injector:parse(self.id, self.props.children) end return Customizable diff --git a/components/widget/widget_table_old.lua b/components/widget/widget_table_old.lua index c3a1a2dca9f..fa211124460 100644 --- a/components/widget/widget_table_old.lua +++ b/components/widget/widget_table_old.lua @@ -51,7 +51,7 @@ function TableOld:_getMaxCells() local getNumberCells = function(row) return row:getCellCount() end - return Array.reduce(Array.map(self.children, getNumberCells), math.max) + return Array.reduce(Array.map(self.props.children, getNumberCells), math.max) end return TableOld diff --git a/components/widget/widget_table_row.lua b/components/widget/widget_table_row.lua index e27f1a94b4b..756f3bac4f4 100644 --- a/components/widget/widget_table_row.lua +++ b/components/widget/widget_table_row.lua @@ -32,7 +32,7 @@ local TableRow = Class.new( ---@return integer function TableRow:getCellCount() - return #self.children + return #self.props.children end ---@return Widget diff --git a/spec/array_spec.lua b/spec/array_spec.lua index c7db394de5d..c0a0dd4a29d 100644 --- a/spec/array_spec.lua +++ b/spec/array_spec.lua @@ -213,4 +213,10 @@ describe('array', function() assert.are_same(a, Array.parseCommaSeparatedString('test1 - test2-test3', '-')) end) end) + + describe('Interleave', function () + it('works', function() + assert.are_same({'a', ' ', 'b', ' ', 'c'}, Array.interleave({'a', 'b', 'c'}, ' ')) + end) + end) end) diff --git a/standard/array.lua b/standard/array.lua index 62ab4854595..a424c97b5ad 100644 --- a/standard/array.lua +++ b/standard/array.lua @@ -664,4 +664,20 @@ function Array.parseCommaSeparatedString(inputString, sep) return Array.map(mw.text.split(inputString, sep or ','), String.trim) end +---Interleaves an array with elements +---Array.interleave({4, 5, 4, 3}, 1) -- {4, 1, 5, 1, 4, 1, 3} +---@generic V, T +---@param elements V[] +---@param x T +---@return (V|T)[] +function Array.interleave(elements, x) + local size = #elements + return Array.flatMap(elements, function(element, index) + if index == size then + return {element} + end + return {element, x} + end) +end + return Array