diff --git a/lua/definitions/mw.lua b/lua/definitions/mw.lua index 9acbdf610f6..05f19caf904 100644 --- a/lua/definitions/mw.lua +++ b/lua/definitions/mw.lua @@ -183,7 +183,7 @@ function mw.html:tag(tagName, args) end ---@param name string ---@param value string|number|nil ---@return self ----@overload fun(self, param: {[string]: string}) +---@overload fun(self, param: {[string]: string|number|nil}) function mw.html:attr(name, value) end ---Get the value of a html attribute previously set using html:attr() with the given name. diff --git a/lua/spec/infobox_shop_merch_spec.lua b/lua/spec/infobox_shop_merch_spec.lua index ef60c0e58d0..deb21f33049 100644 --- a/lua/spec/infobox_shop_merch_spec.lua +++ b/lua/spec/infobox_shop_merch_spec.lua @@ -3,7 +3,13 @@ local ShopMerch = require('Module:Widget/Infobox/ShopMerch') local function render(args) local widget = ShopMerch{args = args} local rendered = widget:render() - return rendered and mw.text.jsonEncode(rendered) or nil + if not rendered then + return + end + for i in ipairs(rendered) do + rendered[i] = tostring(rendered[i]) + end + return mw.text.jsonEncode(rendered) end describe('Infobox/ShopMerch', function() @@ -37,12 +43,14 @@ describe('Infobox/ShopMerch', function() it('uses default shop url when shoplink=true', function() local output = render{shoplink = 'true'} assert.is_not_nil(output) + ---@cast output -nil assert.is_truthy(output:find('https://links.liquipedia.net/tlstore', 1, true)) end) it('strips leading slashes from slugs', function() local output = render{shoplink = '/test'} assert.is_not_nil(output) + ---@cast output -nil assert.is_truthy(output:find('https://links.liquipedia.net/test', 1, true)) local output2 = render{shoplink = '///test'} @@ -61,6 +69,7 @@ describe('Infobox/ShopMerch', function() -- 'ö' is 2 bytes in Lua, but %C3%B6 is 6 chars in URL local output = render{shoplink = 'töst'} assert.is_not_nil(output) + ---@cast output -nil assert.is_truthy(output:find('https://links.liquipedia.net/t%C3%B6st', 1, true)) end) @@ -89,4 +98,4 @@ describe('Infobox/ShopMerch', function() local expanding_slug = string.rep('ö', 500) assert.has_error(function() render{shoplink = expanding_slug} end) end) -end) \ No newline at end of file +end) diff --git a/lua/wikis/commons/Widget/Basic/Box.lua b/lua/wikis/commons/Widget/Basic/Box.lua index 0f2e0043cd3..01d3d57aee4 100644 --- a/lua/wikis/commons/Widget/Basic/Box.lua +++ b/lua/wikis/commons/Widget/Basic/Box.lua @@ -8,10 +8,9 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local Widget = Lua.import('Module:Widget') +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') ---@class BoxProps ---@field children Renderable[]|Renderable @@ -22,31 +21,27 @@ local Widget = Lua.import('Module:Widget') ---@field width string? ---@field height string? ----@class Box: Widget ----@operator call(BoxProps): Box ----@field props BoxProps -local Box = Class.new(Widget) - ----@return Widget|Renderable -function Box:render() - local children = self.props.children +---@param props BoxProps +---@return Renderable +local function Box(props) + local children = props.children if not Array.isArray(children) then - return self.props.children + return props.children end - ---@cast children -Renderable + ---@cast children Renderable[] - return HtmlWidgets.Div{ - css = {['max-width'] = self.props.maxWidth}, + return Html.Div{ + css = {['max-width'] = props.maxWidth}, children = Array.map(children, function(child) - return HtmlWidgets.Div{ + return Html.Div{ classes = {'template-box'}, css = { - ['padding-left'] = self.props.paddingLeft, - ['padding-bottom'] = self.props.paddingBottom, - ['padding-right'] = self.props.paddingRight, - width = self.props.width, - height = self.props.height, - overflow = self.props.height and 'hidden' or nil, + ['padding-left'] = props.paddingLeft, + ['padding-bottom'] = props.paddingBottom, + ['padding-right'] = props.paddingRight, + width = props.width, + height = props.height, + overflow = props.height and 'hidden' or nil, }, children = child, } @@ -54,4 +49,4 @@ function Box:render() } end -return Box +return Component.component(Box) diff --git a/lua/wikis/commons/Widget/Basic/Carousel.lua b/lua/wikis/commons/Widget/Basic/Carousel.lua index 655038121c1..00c2fd54847 100644 --- a/lua/wikis/commons/Widget/Basic/Carousel.lua +++ b/lua/wikis/commons/Widget/Basic/Carousel.lua @@ -8,15 +8,14 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local Table = Lua.import('Module:Table') -local Widget = Lua.import('Module:Widget') +local Component = Lua.import('Module:Widget/Component') local IconWidget = Lua.import('Module:Widget/Image/Icon/Fontawesome') local Button = Lua.import('Module:Widget/Basic/Button') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local Div = HtmlWidgets.Div -local Span = HtmlWidgets.Span +local Html = Lua.import('Module:Widget/Html') +local Div = Html.Div +local Span = Html.Span ---@class CarouselWidgetParameters ---@field children Renderable[] @@ -25,34 +24,29 @@ local Span = HtmlWidgets.Span ---@field classes string[]? ---@field css table? ----@class CarouselWidget: Widget ----@operator call(CarouselWidgetParameters): CarouselWidget ----@field props CarouselWidgetParameters -local Carousel = Class.new(Widget) -Carousel.defaultProps = { +local defaultProps = { itemWidth = '200px', gap = '0.5rem', - classes = {}, - css = {}, } ----@return Widget -function Carousel:render() - assert(self.props.children, 'Carousel: children is required') - assert(Array.isArray(self.props.children), 'Carousel: children must be an array') +---@param props CarouselWidgetParameters +---@return HtmlNode +local function Carousel(props) + assert(props.children, 'Carousel: children is required') + assert(Array.isArray(props.children), 'Carousel: children must be an array') local carouselCss = Table.mergeInto({ - gap = self.props.gap, - }, self.props.css) + gap = props.gap, + }, props.css) local carouselContent = Div{ classes = {'carousel-content'}, css = carouselCss, - children = Array.map(self.props.children, function(child) + children = Array.map(props.children, function(child) return Div{ classes = {'carousel-item'}, css = { - width = self.props.itemWidth, + width = props.itemWidth, }, children = {child}, } @@ -87,7 +81,7 @@ function Carousel:render() local rightFade = Div{classes = {'carousel-fade', 'carousel-fade--right'}} return Div{ - classes = Array.extend({'carousel'}, self.props.classes), + classes = Array.extendWith({'carousel'}, props.classes), children = { leftButton, rightButton, @@ -98,4 +92,4 @@ function Carousel:render() } end -return Carousel +return Component.component(Carousel, defaultProps) diff --git a/lua/wikis/commons/Widget/Basic/CopyToClipboard.lua b/lua/wikis/commons/Widget/Basic/CopyToClipboard.lua index e07d8c16e84..78196823694 100644 --- a/lua/wikis/commons/Widget/Basic/CopyToClipboard.lua +++ b/lua/wikis/commons/Widget/Basic/CopyToClipboard.lua @@ -7,39 +7,32 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') - -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local Span = HtmlWidgets.Span +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') +local Span = Html.Span ---@class CopyToClipboardProps ---@field children Renderable|Renderable[]? ---@field textToCopy string? text to be copied to clipboard ---@field successText string? ----@class CopyToClipboardWidget: Widget ----@operator call(CopyToClipboardProps): CopyToClipboardWidget ----@field props CopyToClipboardProps -local CopyToClipboard = Class.new(Widget) - ----@return Widget -function CopyToClipboard:render() - local props = self.props +---@param props CopyToClipboardProps +---@return HtmlNode +local function CopyToClipboard(props) return Span{ classes = {'copy-to-clipboard'}, attributes = {['data-copied-text'] = props.successText}, children = { Span{ classes = {'copy-this'}, - children = self.props.textToCopy, + children = props.textToCopy, }, Span{ classes = {'see-this'}, - children = self.props.children, + children = props.children, } } } end -return CopyToClipboard +return Component.component(CopyToClipboard) diff --git a/lua/wikis/commons/Widget/Basic/DataTable.lua b/lua/wikis/commons/Widget/Basic/DataTable.lua index 29e8ee6c6b7..f7d98bc4d49 100644 --- a/lua/wikis/commons/Widget/Basic/DataTable.lua +++ b/lua/wikis/commons/Widget/Basic/DataTable.lua @@ -7,39 +7,37 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') -local Widget = Lua.import('Module:Widget') +local Component = Lua.import('Module:Widget/Component') local WidgetUtil = Lua.import('Module:Widget/Util') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local Div = HtmlWidgets.Div -local Table = HtmlWidgets.Table +local Html = Lua.import('Module:Widget/Html') +local Div = Html.Div +local Table = Html.Table ----@class WidgetDataTable: Widget -local DataTable = Class.new(Widget) -DataTable.defaultProps = { - classes = {}, - wrapperClasses = {}, - sortable = false, -} +---@class DataTableProps: HtmlNodeProps +---@field sortable boolean? +---@field tableCss? table +---@field tableAttributes? table +---@field wrapperClasses? string[] ----@return Widget -function DataTable:render() - local isSortable = Logic.readBool(self.props.sortable) +---@param props DataTableProps +---@return HtmlNode +local function DataTable(props) + local isSortable = Logic.readBool(props.sortable) return Div{ children = { Table{ - children = self.props.children, - classes = WidgetUtil.collect('wikitable', isSortable and 'sortable' or nil, self.props.classes), - css = self.props.tableCss, - attributes = self.props.tableAttributes, + children = props.children, + classes = WidgetUtil.collect('wikitable', isSortable and 'sortable' or nil, props.classes), + css = props.tableCss, + attributes = props.tableAttributes, }, }, - classes = WidgetUtil.collect('table-responsive', self.props.wrapperClasses), - attributes = self.props.attributes, - css = self.props.css, + classes = WidgetUtil.collect('table-responsive', props.wrapperClasses), + attributes = props.attributes, + css = props.css, } end -return DataTable +return Component.component(DataTable) diff --git a/lua/wikis/commons/Widget/Basic/Dialog.lua b/lua/wikis/commons/Widget/Basic/Dialog.lua index 6266f0245db..ce7e16ed081 100644 --- a/lua/wikis/commons/Widget/Basic/Dialog.lua +++ b/lua/wikis/commons/Widget/Basic/Dialog.lua @@ -8,12 +8,11 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local Div = HtmlWidgets.Div +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') +local Div = Html.Div ---@class DialogWidgetProps ---@field dialogClasses? string[] @@ -21,14 +20,9 @@ local Div = HtmlWidgets.Div ---@field trigger? Renderable|Renderable[] ---@field children? Renderable|Renderable[] ----@class DialogWidget: Widget ----@operator call(DialogWidgetProps): DialogWidget ----@field props DialogWidgetProps -local DialogWidget = Class.new(Widget) - ----@return Widget? -function DialogWidget:render() - local props = self.props +---@param props DialogWidgetProps +---@return HtmlNode? +local function DialogWidget(props) if Logic.isEmpty(props.title) or Logic.isEmpty(props.trigger) or Logic.isEmpty(props.children) then return end @@ -57,4 +51,4 @@ function DialogWidget:render() } end -return DialogWidget +return Component.component(DialogWidget) diff --git a/lua/wikis/commons/Widget/Basic/Dropdown/Container.lua b/lua/wikis/commons/Widget/Basic/Dropdown/Container.lua index f4469c20c75..410b44d14f4 100644 --- a/lua/wikis/commons/Widget/Basic/Dropdown/Container.lua +++ b/lua/wikis/commons/Widget/Basic/Dropdown/Container.lua @@ -8,16 +8,15 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') local Button = Lua.import('Module:Widget/Basic/Button') local Icon = Lua.import('Module:Widget/Image/Icon/Fontawesome') local WidgetUtil = Lua.import('Module:Widget/Util') -local Div = HtmlWidgets.Div -local Span = HtmlWidgets.Span +local Div = Html.Div +local Span = Html.Span local VARIANT_CONFIG = { inline = { @@ -37,31 +36,28 @@ local VARIANT_CONFIG = { ---@field prefix Renderable|Renderable[]? ---@field label Renderable|Renderable[]? ----@class DropdownContainerWidget: Widget ----@operator call(DropdownContainerWidgetParameters): DropdownContainerWidget ----@field props DropdownContainerWidgetParameters -local DropdownContainer = Class.new(Widget) -DropdownContainer.defaultProps = { +local defaultProps = { variant = 'form', } ----@return Widget|nil -function DropdownContainer:render() - if Logic.isEmpty(self.props.children) then +---@param props DropdownContainerWidgetParameters +---@return HtmlNode? +local function DropdownContainer(props) + if Logic.isEmpty(props.children) then return nil end - local variantConfig = assert(VARIANT_CONFIG[self.props.variant], - 'Invalid Dropdown variant "' .. self.props.variant .. '"') + local variantConfig = assert(VARIANT_CONFIG[props.variant], + 'Invalid Dropdown variant "' .. props.variant .. '"') local toggleChildren = WidgetUtil.collect( - Logic.isNotEmpty(self.props.prefix) and Span{ + Logic.isNotEmpty(props.prefix) and Span{ classes = {'dropdown-widget__prefix'}, - children = self.props.prefix, + children = props.prefix, } or nil, Span{ classes = {'dropdown-widget__label'}, - children = self.props.label, + children = props.label, }, Span{ classes = {'dropdown-widget__indicator'}, @@ -82,16 +78,16 @@ function DropdownContainer:render() } return Div{ - classes = Array.extend({'dropdown-widget', 'dropdown-widget--' .. self.props.variant}, self.props.classes), + classes = Array.extend({'dropdown-widget', 'dropdown-widget--' .. props.variant}, props.classes), children = { toggleButton, Div{ classes = {'dropdown-widget__menu'}, attributes = {['aria-hidden'] = 'true'}, - children = self.props.children + children = props.children } } } end -return DropdownContainer +return Component.component(DropdownContainer, defaultProps) diff --git a/lua/wikis/commons/Widget/Basic/Dropdown/Item.lua b/lua/wikis/commons/Widget/Basic/Dropdown/Item.lua index 6bebed02d45..ee2140f05fe 100644 --- a/lua/wikis/commons/Widget/Basic/Dropdown/Item.lua +++ b/lua/wikis/commons/Widget/Basic/Dropdown/Item.lua @@ -8,58 +8,52 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local Div = HtmlWidgets.Div +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') +local Div = Html.Div local Link = Lua.import('Module:Widget/Basic/Link') local Icon = Lua.import('Module:Widget/Image/Icon/Fontawesome') local WidgetUtil = Lua.import('Module:Widget/Util') ----@class DropdownItemWidgetParameters +---@class DropdownItemProps: LinkComponentProps ---@field icon string|Widget? ----@field children Renderable|Renderable[] ----@field link string? ----@field linktype 'internal'|'external'|nil ---@field classes string[]? ---@field attributes table? ----@class DropdownItemWidget: Widget ----@operator call(DropdownItemWidgetParameters): DropdownItemWidget ----@field props DropdownItemWidgetParameters -local DropdownItem = Class.new(Widget) -DropdownItem.defaultProps = { +local defaultProps = { linktype = 'internal', } ----@return Widget -function DropdownItem:render() - local icon = not Logic.isEmpty(self.props.icon) and - (type(self.props.icon) == 'string' and Icon{iconName = self.props.icon, size = 'sm'} or self.props.icon) +---@param props DropdownItemProps +---@return HtmlNode +local function DropdownItem(props) + local icon = Logic.isNotEmpty(props.icon) and + (type(props.icon) == 'string' and Icon{iconName = props.icon --[[@as string]], size = 'sm'} or props.icon) + or nil - local children = WidgetUtil.collect(icon, self.props.children) + local children = WidgetUtil.collect(icon, props.children) local item = Div{ - classes = Array.extend('dropdown-widget__item', self.props.classes), - attributes = self.props.attributes, + classes = Array.extend('dropdown-widget__item', props.classes), + attributes = props.attributes, children = children } - if not self.props.link then + if not props.link then return item end return Div{ children = { Link{ - link = self.props.link, - linktype = self.props.linktype, + link = props.link, + linktype = props.linktype, children = {item} } } } end -return DropdownItem +return Component.component(DropdownItem, defaultProps) diff --git a/lua/wikis/commons/Widget/Basic/Label.lua b/lua/wikis/commons/Widget/Basic/Label.lua index ea8d72a072f..319a03c93fe 100644 --- a/lua/wikis/commons/Widget/Basic/Label.lua +++ b/lua/wikis/commons/Widget/Basic/Label.lua @@ -7,27 +7,17 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') - ----@class GenericLabelProps ----@field attributes table? ----@field css table? ----@field children Renderable|Renderable[] +---@class GenericLabelProps: HtmlNodeProps ---@field labelScheme string? ---@field labelScale number? ---@field labelType string? ----@class GenericLabel: Widget ----@operator call(GenericLabelProps): GenericLabel ----@field props GenericLabelProps -local GenericLabel = Class.new(Widget) - ----@return Widget -function GenericLabel:render() - local props = self.props +---@param props GenericLabelProps +---@return HtmlNode +local function GenericLabel(props) if props.labelScale then props.css = props.css or {} props.css['--label-scale'] = props.labelScale @@ -37,7 +27,7 @@ function GenericLabel:render() props.attributes['data-label-type'] = props.labelType end - return HtmlWidgets.Div{ + return Html.Div{ attributes = props.attributes, classes = { 'generic-label', @@ -48,4 +38,4 @@ function GenericLabel:render() } end -return GenericLabel +return Component.component(GenericLabel) diff --git a/lua/wikis/commons/Widget/Basic/Link.lua b/lua/wikis/commons/Widget/Basic/Link.lua index a65ab99e377..654d4985fdf 100644 --- a/lua/wikis/commons/Widget/Basic/Link.lua +++ b/lua/wikis/commons/Widget/Basic/Link.lua @@ -7,51 +7,43 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') +local Logic = Lua.import('Module:Logic') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local Fragment = HtmlWidgets.Fragment -local Widget = Lua.import('Module:Widget') +local Component = Lua.import('Module:Widget/Component') local WidgetUtil = Lua.import('Module:Widget/Util') ----@class LinkWidgetParameters ----@field children Renderable|Renderable[] ----@field link string ----@field linktype 'internal'|'external'|nil +---@class LinkComponentProps +---@field children? Renderable|Renderable[] +---@field link? string +---@field linktype? 'internal'|'external' ----@class LinkWidget: Widget ----@operator call(LinkWidgetParameters): LinkWidget -local Link = Class.new(Widget) -Link.defaultProps = { +local defaultProps = { linktype = 'internal', } ----@return Widget? -function Link:render() - if not self.props.link then +---@param props LinkComponentProps +---@return Renderable[]? +local function Link(props) + if Logic.isEmpty(props.link) then return end - if self.props.linktype == 'external' then - return Fragment{ - children = WidgetUtil.collect( - '[', - (self.props.link:gsub(' ', '%%20')), - ' ', - unpack(self.props.children), - ']' - ) - } + if props.linktype == 'external' then + return WidgetUtil.collect( + '[', + (props.link:gsub(' ', '%%20')), + ' ', + props.children, + ']' + ) end - return Fragment{ - children = WidgetUtil.collect( - '[[', - self.props.link, - '|', - unpack(self.props.children) or self.props.link, - ']]' - ) - } + return WidgetUtil.collect( + '[[', + props.link, + '|', + Logic.emptyOr(props.children, props.link), + ']]' + ) end -return Link +return Component.component(Link, defaultProps) diff --git a/lua/wikis/commons/Widget/Basic/Slider.lua b/lua/wikis/commons/Widget/Basic/Slider.lua index 0cdb0da184f..4214aa84778 100644 --- a/lua/wikis/commons/Widget/Basic/Slider.lua +++ b/lua/wikis/commons/Widget/Basic/Slider.lua @@ -8,11 +8,10 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local Div = HtmlWidgets.Div +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') +local Div = Html.Div ---@class SliderWidgetParameters ---@field id string @@ -24,22 +23,18 @@ local Div = HtmlWidgets.Div ---@field title fun(value: integer): string ---@field childrenAtValue fun(value: integer): Widget|Widget[]|nil ----@class SliderWidget: Widget ----@operator call(SliderWidgetParameters): SliderWidget ----@field props SliderWidgetParameters -local Slider = Class.new(Widget) - ----@return Widget -function Slider:render() - assert(self.props.id, 'Slider requires a unique id property') +---@param props SliderWidgetParameters +---@return HtmlNode +local function Slider(props) + assert(props.id, 'Slider requires a unique id property') -- We make the real slider in js - local min, max, step = self.props.min or 0, self.props.max or 100, self.props.step or 1 + local min, max, step = props.min or 0, props.max or 100, props.step or 1 local children = {} for value = min, max, step do table.insert(children, { - content = self.props.childrenAtValue(value) or '', - title = self.props.title and self.props.title(value) or value, + content = props.childrenAtValue(value) or '', + title = props.title and props.title(value) or value, value = value, }) end @@ -47,15 +42,15 @@ function Slider:render() return Div{ classes = { 'slider' }, attributes = { - ['data-id'] = self.props.id, + ['data-id'] = props.id, ['data-min'] = min, ['data-max'] = max, ['data-step'] = step, - ['data-value'] = self.props.defaultValue or self.props.min or 0, + ['data-value'] = props.defaultValue or props.min or 0, }, children = Array.map(children, function(child) - return HtmlWidgets.Div{ - classes = { 'slider-value', 'slider-value--' .. child.value, self.props.class }, + return Div{ + classes = { 'slider-value', 'slider-value--' .. child.value, props.class }, attributes = { ['data-title'] = child.title, }, @@ -65,4 +60,4 @@ function Slider:render() } end -return Slider +return Component.component(Slider) diff --git a/lua/wikis/commons/Widget/Component.lua b/lua/wikis/commons/Widget/Component.lua index eb2a702a01e..d4011d1e67d 100644 --- a/lua/wikis/commons/Widget/Component.lua +++ b/lua/wikis/commons/Widget/Component.lua @@ -9,22 +9,27 @@ ---@alias ContextDef {defaultValue: T} ---@alias ContextParam {def: ContextDef, value: T, children?: Renderable|Renderable[]} ----@alias HtmlParam {classes?: string[], css?: table, attributes?: table, children?: Renderable|Renderable[]} ---@alias ErrorParam {children?: Renderable|Renderable[], fallback?: fun(error: Error, context: Context?): Renderable} ---@class VNode

---@field renderFn string|fun(props: P, context?: Context?): Renderable ---@field props P +---@class HtmlNodeProps +---@field classes? string[] +---@field css? table +---@field attributes? table +---@field children? Renderable|Renderable[] + ---@alias Context {props:{parent: Context?, def: ContextDef, value: T}} ---@alias ContextNode VNode> ----@alias HtmlNode VNode +---@alias HtmlNode VNode ---@alias ErrorBoundaryNode VNode ---@alias Component

fun(props?: P, context: Context?): VNode

---@alias ContextComponent Component> ----@alias HtmlComponent Component +---@alias HtmlComponent Component ---@alias ErrorBoundaryComponent Component local Lua = require('Module:Lua') @@ -33,6 +38,7 @@ local Renderer = Lua.import('Module:Widget/Renderer') local ComponentCore = {} -- Virtual Nodes (The table returned after calling a component) +---@package ComponentCore.VNodeMT = { -- Automatically trigger rendering __tostring = Renderer.render, diff --git a/lua/wikis/commons/Widget/GeneralCollapsible/ChevronToggle.lua b/lua/wikis/commons/Widget/GeneralCollapsible/ChevronToggle.lua index e3c98047382..062c0981c51 100644 --- a/lua/wikis/commons/Widget/GeneralCollapsible/ChevronToggle.lua +++ b/lua/wikis/commons/Widget/GeneralCollapsible/ChevronToggle.lua @@ -7,20 +7,14 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') - -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') local Button = Lua.import('Module:Widget/Basic/Button') local Icon = Lua.import('Module:Widget/Image/Icon/Fontawesome') -local Span = HtmlWidgets.Span - ----@class ChevronToggle: Widget ----@operator call(table?): ChevronToggle -local ChevronToggle = Class.new(Widget) +local Span = Html.Span ----@return Widget -function ChevronToggle:render() +---@return HtmlNode +local function ChevronToggle() local expandButton = Button{ classes = {'general-collapsible-expand-button'}, children = Span{ @@ -51,4 +45,4 @@ function ChevronToggle:render() } end -return ChevronToggle +return Component.component(ChevronToggle) diff --git a/lua/wikis/commons/Widget/GeneralCollapsible/Default.lua b/lua/wikis/commons/Widget/GeneralCollapsible/Default.lua index 2fecc9b415c..301a1ab6f99 100644 --- a/lua/wikis/commons/Widget/GeneralCollapsible/Default.lua +++ b/lua/wikis/commons/Widget/GeneralCollapsible/Default.lua @@ -8,54 +8,45 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local CollapsibleToggle = Lua.import('Module:Widget/GeneralCollapsible/Toggle') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') -local B = HtmlWidgets.B -local Div = HtmlWidgets.Div -local Widget = Lua.import('Module:Widget') +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') +local B = Html.B +local Div = Html.Div ----@class DefaultCollapsibleProps ----@field attributes table? ----@field classes string[]? ----@field css table? +---@class DefaultCollapsibleProps: HtmlNodeProps ---@field shouldCollapse boolean? ----@field title Renderable? +---@field title Renderable|Renderable[]? ---@field titleClasses string[]? ---@field titleWidget Renderable? ---@field collapseAreaClasses string[]? ---@field collapseAreaCss table? ---@field children Renderable|Renderable[]? ----@class DefaultCollapsible: Widget ----@operator call(DefaultCollapsibleProps?): DefaultCollapsible ----@field props DefaultCollapsibleProps -local DefaultCollapsible = Class.new(Widget) - ----@return Widget -function DefaultCollapsible:render() - local props = self.props +---@param props DefaultCollapsibleProps +---@return HtmlNode +local function DefaultCollapsible(props) return Div{ attributes = props.attributes, css = props.css, - classes = Array.extend({}, + classes = Array.extend( 'general-collapsible', props.shouldCollapse and 'collapsed' or nil, props.classes ), children = { props.titleWidget or Div{ - classes = Array.extend({'general-collapsible-default-header'}, props.titleClasses), + classes = Array.extend('general-collapsible-default-header', props.titleClasses), children = { - B{children = {props.title}, classes = {'general-collapsible-default-title'}}, + B{children = props.title, classes = {'general-collapsible-default-title'}}, CollapsibleToggle{css = {float = 'right'}}, } }, Div{ children = props.children, css = props.collapseAreaCss, - classes = Array.extend({}, + classes = Array.extend( 'should-collapse', props.collapseAreaClasses ), @@ -64,4 +55,4 @@ function DefaultCollapsible:render() } end -return DefaultCollapsible +return Component.component(DefaultCollapsible) diff --git a/lua/wikis/commons/Widget/GeneralCollapsible/Toggle.lua b/lua/wikis/commons/Widget/GeneralCollapsible/Toggle.lua index 0142e134286..5733d7eaa52 100644 --- a/lua/wikis/commons/Widget/GeneralCollapsible/Toggle.lua +++ b/lua/wikis/commons/Widget/GeneralCollapsible/Toggle.lua @@ -7,25 +7,26 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') +local Array = Lua.import('Module:Array') local Logic = Lua.import('Module:Logic') -local Widget = Lua.import('Module:Widget') -local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Component = Lua.import('Module:Widget/Component') +local Html = Lua.import('Module:Widget/Html') local Button = Lua.import('Module:Widget/Basic/Button') local Icon = Lua.import('Module:Widget/Image/Icon/Fontawesome') -local Span = HtmlWidgets.Span +local Span = Html.Span ----@class CollapsibleToggle: Widget ----@operator call(table?): CollapsibleToggle -local CollapsibleToggle = Class.new(Widget) +---@class CollapsibleToggleProps: HtmlNodeProps +---@field showButtonChildren? Renderable|Renderable[] +---@field hideButtonChildren? Renderable|Renderable[] ----@return Widget -function CollapsibleToggle:render() +---@param props CollapsibleToggleProps +---@return HtmlNode +local function CollapsibleToggle(props) local showButton = Button{ classes = {'general-collapsible-expand-button'}, children = Span{ - children = Logic.emptyOr(self.props.showButtonChildren, { + children = Logic.emptyOr(props.showButtonChildren, { Icon{iconName = 'show'}, ' ', 'Show' @@ -37,7 +38,7 @@ function CollapsibleToggle:render() local hideButton = Button{ classes = {'general-collapsible-collapse-button'}, children = Span{ - children = Logic.emptyOr(self.props.hideButtonChildren, { + children = Logic.emptyOr(props.hideButtonChildren, { Icon{iconName = 'hide'}, ' ', 'Hide' @@ -48,9 +49,12 @@ function CollapsibleToggle:render() } return Span{ - classes = {'general-collapsible-default-toggle', unpack(self.props.classes or {})}, - css = self.props.css, - attributes = self.props.attributes, + classes = Array.extend( + 'general-collapsible-default-toggle', + props.classes + ), + css = props.css, + attributes = props.attributes, children = { showButton, hideButton, @@ -58,4 +62,4 @@ function CollapsibleToggle:render() } end -return CollapsibleToggle +return Component.component(CollapsibleToggle) diff --git a/lua/wikis/commons/Widget/Image/Icon/Fontawesome.lua b/lua/wikis/commons/Widget/Image/Icon/Fontawesome.lua index 64a3ba25817..c5d670db07a 100644 --- a/lua/wikis/commons/Widget/Image/Icon/Fontawesome.lua +++ b/lua/wikis/commons/Widget/Image/Icon/Fontawesome.lua @@ -7,19 +7,7 @@ local Lua = require('Module:Lua') -local Class = Lua.import('Module:Class') - local Icon = Lua.import('Module:Icon') -local WidgetIcon = Lua.import('Module:Widget/Image/Icon') - ----@class IconFontawesomeWidget: IconWidget ----@operator call(IconProps): IconFontawesomeWidget ----@field props IconProps -local FontawesomeIcon = Class.new(WidgetIcon) - ----@return string? -function FontawesomeIcon:render() - return Icon.makeIcon(self.props) -end +local Component = Lua.import('Module:Widget/Component') -return FontawesomeIcon +return Component.component(Icon.makeIcon) diff --git a/lua/wikis/commons/Widget/Image/Icon/Image.lua b/lua/wikis/commons/Widget/Image/Icon/Image.lua index 78ef8b15cb9..93fcc874668 100644 --- a/lua/wikis/commons/Widget/Image/Icon/Image.lua +++ b/lua/wikis/commons/Widget/Image/Icon/Image.lua @@ -8,17 +8,16 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') -local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') -local WidgetIcon = Lua.import('Module:Widget/Image/Icon') +local Component = Lua.import('Module:Widget/Component') ---@class IconImageWidgetParameters ---@field imageLight string? ---@field imageDark string? ----@field link string +---@field link string? ---@field alt string? ----@field classes string[] +---@field classes string[]? ---@field border 'border'? # only available if `format: 'frameless'?` ---@field format 'frameless'|'frame'|'thumb'? ---@field size string? # '{width}px'|'x{height}px'|'{width}x{height}px' @@ -26,59 +25,58 @@ local WidgetIcon = Lua.import('Module:Widget/Image/Icon') ---@field verticalAlignment 'baseline'|'sub'|'super'|'top'|'text-top'|'middle'|'bottom'|'text-bottom'? ---@field caption string? ----@class IconImageWidget: IconWidget ----@operator call(IconImageWidgetParameters): IconImageWidget ----@field props IconImageWidgetParameters -local Icon = Class.new(WidgetIcon) +local Icon = {} Icon.defaultProps = { link = '', size = 'x20px', - classes = {}, verticalAlignment = 'middle', -- make the implicit mw default explicit } +---@param props IconImageWidgetParameters ---@return string|string[]? -function Icon:render() - local imageLight = self.props.imageLight - local imageDark = self.props.imageDark +function Icon.render(props) + local imageLight = props.imageLight + local imageDark = props.imageDark if Logic.isEmpty(imageLight) or Logic.isEmpty(imageDark) or imageLight == imageDark then - return self:_make(Logic.emptyOr(imageLight, imageDark)) + return Icon._make(props, Logic.emptyOr(imageLight, imageDark)) end return { - self:_make(imageLight, 'show-when-light-mode'), - self:_make(imageDark, 'show-when-dark-mode'), + Icon._make(props, imageLight, 'show-when-light-mode'), + Icon._make(props, imageDark, 'show-when-dark-mode'), } end +---@private +---@param props IconImageWidgetParameters ---@param image string? ---@param themeClass string? ---@return string? ---@overload fun(nil): nil -function Icon:_make(image, themeClass) +function Icon._make(props, image, themeClass) if Logic.isEmpty(image) then return end - local classes = table.concat(Array.extend(self.props.classes, themeClass), ' ') + local classes = table.concat(Array.extend(props.classes, themeClass), ' ') - local border = Logic.nilIfEmpty(self.props.border) - assert((self.props.format == 'frameless' or not self.props.format) or not border, + local border = Logic.nilIfEmpty(props.border) + assert((props.format == 'frameless' or not props.format) or not border, 'border can only be used for frameless images') local parts = Array.extend( 'File:' .. image, border, - Logic.nilIfEmpty(self.props.format), - Logic.isNumeric(self.props.size) and (self.props.size .. 'px') or Logic.nilIfEmpty(self.props.size), - Logic.nilIfEmpty(self.props.horizontalAlignment), - self.props.verticalAlignment ~= 'middle' and self.props.verticalAlignment or nil, - 'link=' .. self.props.link, - Logic.isNotEmpty(self.props.alt) and ('alt=' .. self.props.alt) or nil, + Logic.nilIfEmpty(props.format), + Logic.isNumeric(props.size) and (props.size .. 'px') or Logic.nilIfEmpty(props.size), + Logic.nilIfEmpty(props.horizontalAlignment), + props.verticalAlignment ~= 'middle' and props.verticalAlignment or nil, + 'link=' .. props.link, + Logic.isNotEmpty(props.alt) and ('alt=' .. props.alt) or nil, Logic.isNotEmpty(classes) and ('class=' .. classes) or nil, - Logic.nilIfEmpty(self.props.caption) + Logic.nilIfEmpty(props.caption) ) return '[[' .. table.concat(parts, '|') .. ']]' end -return Icon +return Component.component(Icon.render, Icon.defaultProps) diff --git a/lua/wikis/commons/Widget/Match/Page/SeriesDots.lua b/lua/wikis/commons/Widget/Match/Page/SeriesDots.lua index a029c041786..e9ab306f54d 100644 --- a/lua/wikis/commons/Widget/Match/Page/SeriesDots.lua +++ b/lua/wikis/commons/Widget/Match/Page/SeriesDots.lua @@ -35,7 +35,7 @@ local MatchPageSeriesDots = Class.new(Widget) ---@private ---@param result string ----@return Widget +---@return VNode MatchPageSeriesDots._makeGameResultIcon = FnUtil.memoize(function (result) return Label{labelType = 'result-' .. RESULT_DISPLAY_TYPES[result:lower()]} end) diff --git a/lua/wikis/commons/Widget/Match/Summary/GameWinLossIndicator.lua b/lua/wikis/commons/Widget/Match/Summary/GameWinLossIndicator.lua index 9ea8a64b724..d72352ddccd 100644 --- a/lua/wikis/commons/Widget/Match/Summary/GameWinLossIndicator.lua +++ b/lua/wikis/commons/Widget/Match/Summary/GameWinLossIndicator.lua @@ -24,7 +24,7 @@ local LABELS = { ---@operator call(table): MatchSummaryGameWinLossIndicator local MatchSummaryGameWinLossIndicator = Class.new(Widget) ----@return Widget +---@return VNode function MatchSummaryGameWinLossIndicator:render() local winner = self.props.winner diff --git a/lua/wikis/commons/Widget/Match/Summary/VetoLabel.lua b/lua/wikis/commons/Widget/Match/Summary/VetoLabel.lua index 9f4283510ec..06dba4def4b 100644 --- a/lua/wikis/commons/Widget/Match/Summary/VetoLabel.lua +++ b/lua/wikis/commons/Widget/Match/Summary/VetoLabel.lua @@ -28,7 +28,7 @@ local VetoTypes = { ---@field props {vetoType: VetoTypes?} local MatchSummaryVetoLabel = Class.new(Widget) ----@return Widget? +---@return VNode? function MatchSummaryVetoLabel:render() local vetoType = self.props.vetoType if not VetoTypes[vetoType] then diff --git a/lua/wikis/commons/Widget/Standings/MatchOverview.lua b/lua/wikis/commons/Widget/Standings/MatchOverview.lua index abd40d17efa..27e98586fa4 100644 --- a/lua/wikis/commons/Widget/Standings/MatchOverview.lua +++ b/lua/wikis/commons/Widget/Standings/MatchOverview.lua @@ -98,7 +98,7 @@ function MatchOverviewWidget:_createScoreContainer(leftScore, rightScore) end ---@private ----@return Widget? +---@return VNode? function MatchOverviewWidget:_createResultDisplay(leftScore, rightScore) if self.props.match.phase == 'upcoming' then return