From 66a3e288878bfae37d9e2293bf4bb278ef9186e5 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 11 Oct 2025 10:59:35 +0900 Subject: [PATCH 01/52] use anyOf --- lua/wikis/commons/MatchTable.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 41bb8983ec9..00c8acb99e5 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -42,6 +42,7 @@ local ConditionNode = Condition.Node local Comparator = Condition.Comparator local BooleanOperator = Condition.BooleanOperator local ColumnName = Condition.ColumnName +local ConditionUtil = Condition.Util local DRAW_WINNER = 0 local INVALID_TIER_DISPLAY = 'Undefined' @@ -391,10 +392,7 @@ function MatchTable:buildAdditionalConditions() local getOrCondition = function(lpdbKey, input) if Logic.isEmpty(input) then return end - local orConditions = ConditionTree(BooleanOperator.any) - Array.forEach(mw.text.split(input, ','), function(value) - orConditions:add{ConditionNode(ColumnName(lpdbKey), Comparator.eq, String.trim(value))} - end) + local orConditions = ConditionUtil.anyOf(ColumnName(lpdbKey), Array.parseCommaSeparatedString(input, ',')) conditions:add(orConditions) end From acc2f4870c716edb558d6c2a8f7a3f25ad1b5a00 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 11 Oct 2025 11:07:06 +0900 Subject: [PATCH 02/52] parseCommaSeparatedString --- lua/wikis/commons/MatchTable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 00c8acb99e5..c5d96070772 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -202,7 +202,7 @@ function MatchTable:_readOpponentInputsFromBase(base) if Logic.isNotEmpty(inputs) or Logic.isEmpty(self.args[base .. 's']) then return inputs end - return Array.map(mw.text.split(self.args[base .. 's'], ',', true), String.trim) + return Array.parseCommaSeparatedString(self.args[base .. 's']) end ---@param mode MatchTableMode @@ -224,7 +224,7 @@ function MatchTable:readAliases(mode) local aliases = {} if String.isEmpty(self.args.aliases) then return aliases end - local aliasInput = Array.map(mw.text.split(self.args.aliases, ','), String.trim) + local aliasInput = Array.parseCommaSeparatedString(self.args.aliases) Array.forEach(aliasInput, function(alias) alias = alias:gsub(' ', '_') From 5fb3e9514eab6519a33beeef0598b510f12ea2f4 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 11 Oct 2025 16:14:10 +0900 Subject: [PATCH 03/52] code conciseness Co-authored-by: hjpalpha <75081997+hjpalpha@users.noreply.github.com> --- lua/wikis/commons/MatchTable.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index c5d96070772..aee67c22166 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -392,8 +392,7 @@ function MatchTable:buildAdditionalConditions() local getOrCondition = function(lpdbKey, input) if Logic.isEmpty(input) then return end - local orConditions = ConditionUtil.anyOf(ColumnName(lpdbKey), Array.parseCommaSeparatedString(input, ',')) - conditions:add(orConditions) + conditions:add(ConditionUtil.anyOf(ColumnName(lpdbKey), Array.parseCommaSeparatedString(input, ','))) end getOrCondition('liquipediatier', args.tier) From 49ffcbbf4fc0b8d6b52b6cc50d6f931075b23d55 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:38:37 +0900 Subject: [PATCH 04/52] remove redundant comma --- lua/wikis/commons/MatchTable.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index aee67c22166..c4dece9de2d 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -392,7 +392,7 @@ function MatchTable:buildAdditionalConditions() local getOrCondition = function(lpdbKey, input) if Logic.isEmpty(input) then return end - conditions:add(ConditionUtil.anyOf(ColumnName(lpdbKey), Array.parseCommaSeparatedString(input, ','))) + conditions:add(ConditionUtil.anyOf(ColumnName(lpdbKey), Array.parseCommaSeparatedString(input))) end getOrCondition('liquipediatier', args.tier) From 78219f8090c717d355a695dc8b37d69330f16e4f Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:41:13 +0900 Subject: [PATCH 05/52] commaSeparatedString --- lua/wikis/commons/MatchTable.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index c4dece9de2d..46699d8751e 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -283,10 +283,10 @@ function MatchTable:readTimeRange() end --build year range from subpage name (or input) - local yearRange = Array.map(mw.text.split(yearsString, '-'), String.trim) - yearRange = { - tonumber(yearRange[1]), - tonumber(yearRange[2] or yearRange[1]), + local yearInput = Array.parseCommaSeparatedString(yearsString, '-') + local yearRange = { + tonumber(yearInput[1]), + tonumber(yearInput[2] or yearInput[1]), } --sort @@ -386,8 +386,9 @@ end ---@return ConditionTree? function MatchTable:buildAdditionalConditions() local args = self.args - local conditions = ConditionTree(BooleanOperator.all) - :add{ConditionNode(ColumnName('status'), Comparator.neq, 'notplayed')} + local conditions = ConditionTree(BooleanOperator.all):add( + ConditionNode(ColumnName('status'), Comparator.neq, 'notplayed') + ) local getOrCondition = function(lpdbKey, input) if Logic.isEmpty(input) then return end From a228469961e091c58fa6474230cceff4b5472a3a Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:42:48 +0900 Subject: [PATCH 06/52] clean up condition building --- lua/wikis/commons/MatchTable.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 46699d8751e..1f8cb77b786 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -305,7 +305,7 @@ function MatchTable:query() self.matches = {} Lpdb.executeMassQuery('match2', { - conditions = self:buildConditions(), + conditions = tostring(self:buildConditions()), order = 'date desc', query = 'match2id, match2opponents, match2games, date, dateexact, icon, icondark, liquipediatier, game, type,' .. 'liquipediatiertype, tournament, pagename, parent, section, tickername, vod, winner, match2bracketdata,' @@ -327,14 +327,13 @@ function MatchTable:query() return self end ----@return string +---@return ConditionTree function MatchTable:buildConditions() return ConditionTree(BooleanOperator.all) - :add{ConditionNode(ColumnName('finished'), Comparator.eq, 1)} - :add{self:buildDateConditions()} - :add{self:buildOpponentConditions()} - :add{self:buildAdditionalConditions()} - :toString() + :add(ConditionNode(ColumnName('finished'), Comparator.eq, 1)) + :add(self:buildDateConditions()) + :add(self:buildOpponentConditions()) + :add(self:buildAdditionalConditions()) end ---@return ConditionTree? From 8e160d54311d31e27f67f81f06b86dd341d84f0e Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 16:02:19 +0900 Subject: [PATCH 07/52] use MGUMatch for processing --- lua/wikis/commons/MatchTable.lua | 54 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 1f8cb77b786..7edebcd53cf 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -88,11 +88,11 @@ local SECONDS_ONE_DAY = 3600 * 24 ---@field result MatchTableMatchResult ---@class MatchTableMatchResult ----@field opponent match2opponent +---@field opponent standardOpponent ---@field gameOpponents table[] ----@field vs match2opponent +---@field vs standardOpponent ---@field gameVsOpponents table[] ----@field winner number +---@field winner number? ---@field countGames boolean ---@field countRounds boolean @@ -409,21 +409,17 @@ function MatchTable:buildAdditionalConditions() return conditions end ----@param record table +---@param record match2 ---@return MatchTableMatch? function MatchTable:matchFromRecord(record) - local result = self:resultFromRecord(record) + local match = MatchGroupUtil.matchFromRecord(record) --[[@as MatchTableMatch]] + local result = self:resultFromRecord(match) if not result then return end - record.extradata = record.extradata or {} - - ---@type MatchTableMatch - local match = Table.merge({ - vods = self:vodsFromRecord(record), - result = result, - }, MatchGroupUtil.matchFromRecord(record)) + match.result = result + match.vods = self:vodsFromRecord(match) local tournament = Tournament.partialTournamentFromMatch(match) @@ -437,16 +433,16 @@ function MatchTable:matchFromRecord(record) return match end ----@param record table +---@param record MatchGroupUtilMatch ---@return {index: number, link: string}[] function MatchTable:vodsFromRecord(record) local vods = {} if String.nilIfEmpty(record.vod) then - vods = {{index = 0, link = record.vod}} + vods[1] = {index = 0, link = record.vod} end - Array.forEach(record.match2games, function(game, gameIndex) - if String.nilIfEmpty(game.vod) then + Array.forEach(record.games, function(game, gameIndex) + if String.isNotEmpty(game.vod) then table.insert(vods, {link = game.vod, index = gameIndex}) end end) @@ -454,10 +450,10 @@ function MatchTable:vodsFromRecord(record) return vods end ----@param record table +---@param record MatchGroupUtilMatch ---@return MatchTableMatchResult? function MatchTable:resultFromRecord(record) - if #record.match2opponents ~= 2 then + if #record.opponents ~= 2 then return self:resultFromNonStandardRecord(record) end @@ -465,22 +461,24 @@ function MatchTable:resultFromRecord(record) local countGames = false local countRounds = false + ---@param opponentRecord standardOpponent + ---@return boolean local foundInAlias = function(opponentRecord) if aliases[opponentRecord.name] then countGames = true countRounds = self.config.showRoundStats return true end - return self.config.mode == Opponent.solo and Array.any(opponentRecord.match2players, function(player) - return aliases[player.name] or false + return self.config.mode == Opponent.solo and Array.any(opponentRecord.players, function(player) + return aliases[player.pageName] or false end) end - local winner = tonumber(record.winner) + local winner = record.winner local indexes - if foundInAlias(record.match2opponents[1]) then + if foundInAlias(record.opponents[1]) then indexes = {1, 2} - elseif foundInAlias(record.match2opponents[2]) then + elseif foundInAlias(record.opponents[2]) then indexes = {2, 1} winner = winner == 2 and 1 or winner == 1 and 2 or winner else @@ -489,10 +487,12 @@ function MatchTable:resultFromRecord(record) return end - local gameOpponents = Array.map(record.match2games, Operator.property('opponents')) + local gameOpponents = Array.map(record.games, Operator.property('opponents')) + + ---@type MatchTableMatchResult local result = { - opponent = record.match2opponents[indexes[1]], - vs = record.match2opponents[indexes[2]], + opponent = record.opponents[indexes[1]], + vs = record.opponents[indexes[2]], winner = winner, countGames = countGames, countRounds = countRounds, @@ -504,7 +504,7 @@ function MatchTable:resultFromRecord(record) end ---overwritable for wikis that have BR/FFA matches ----@param record table +---@param record MatchGroupUtilMatch ---@return table? function MatchTable:resultFromNonStandardRecord(record) end From 307616277f0ea2d4eeb8e2623854e584efaa26aa Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 16:05:30 +0900 Subject: [PATCH 08/52] type conciseness --- lua/wikis/commons/MatchTable.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 7edebcd53cf..c24b739e806 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -53,6 +53,7 @@ local BO1_SCORE_CONCAT = ' - ' local SECONDS_ONE_DAY = 3600 * 24 ---@alias MatchTableMode `Opponent.solo` | `Opponent.team` +---@alias WDLCount {w: number, d: number, l: number} ---@class MatchTableConfig ---@field mode MatchTableMode @@ -509,7 +510,7 @@ end function MatchTable:resultFromNonStandardRecord(record) end ----@return {matches: {w: number, d: number, l: number}, games: {w: number, d: number, l: number}} +---@return {matches: WDLCount, games: WDLCount, rounds: WDLCount} function MatchTable:statsFromMatches() local totalMatches = {w = 0, d = 0, l = 0} local totalGames = {w = 0, d = 0, l = 0} From 0c69d0f1a2ed8b302b853f5294e6bb844141bc44 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 16:05:40 +0900 Subject: [PATCH 09/52] type annotation --- lua/wikis/commons/MatchTable.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index c24b739e806..5c670c474f9 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -520,6 +520,8 @@ function MatchTable:statsFromMatches() return math.max(tonumber(value) or 0, 0) end + ---@param opponent standardOpponent + ---@return boolean local hasWalkoverStatus = function(opponent) return Logic.isNotEmpty(opponent.status) and opponent.status ~= 'S' end From 6367a16c2bb84a1de004d12b360bf229e67bd73d Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 16:10:57 +0900 Subject: [PATCH 10/52] use standardOpponent --- lua/wikis/commons/MatchTable.lua | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 5c670c474f9..a5191a20c11 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -781,12 +781,11 @@ function MatchTable:nonStandardMatch(match) :wikitext('') end ----@param opponentRecord match2opponent +---@param opponent standardOpponent ---@param flipped boolean? ---@return Html -function MatchTable:_displayOpponent(opponentRecord, flipped) +function MatchTable:_displayOpponent(opponent, flipped) local cell = mw.html.create('td') - local opponent = Opponent.fromMatch2Record(opponentRecord) if Logic.isEmpty(opponent) then return cell:wikitext('Unknown') end return cell @@ -807,13 +806,13 @@ function MatchTable:_displayScore(match) return opponent.status == 'S' end) local bestof1Score = match.bestof == 1 and Info.config.match2.gameScoresIfBo1 and hasOnlyScores - ---@param opponentRecord match2opponent + ---@param opponent standardOpponent ---@param gameOpponents table[] ---@return Html|string - local toScore = function(opponentRecord, gameOpponents) - if Table.isEmpty(opponentRecord) or not opponentRecord.status then return 'Unkn' end - local score = OpponentDisplay.InlineScore(opponentRecord) - local status = opponentRecord.status + local toScore = function(opponent, gameOpponents) + if Table.isEmpty(opponent) or not opponent.status then return 'Unkn' end + local score = OpponentDisplay.InlineScore(opponent) + local status = opponent.status local game1Opponent = gameOpponents[1] if bestof1Score and game1Opponent then @@ -821,7 +820,7 @@ function MatchTable:_displayScore(match) status = game1Opponent.status end - return mw.html.create(tonumber(opponentRecord.placement) == 1 and 'b' or nil) + return mw.html.create(tonumber(opponent.placement) == 1 and 'b' or nil) :wikitext(status == SCORE_STATUS and (score or '–') or status) end From 8d41a9d8d5c9e2b651551861045ef2f899b19c6b Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 16:53:42 +0900 Subject: [PATCH 11/52] extract matchtable style to separate file --- stylesheets/Main.scss | 1 + stylesheets/commons/MatchTable.scss | 14 ++++++++++++++ stylesheets/commons/Miscellaneous.scss | 16 ---------------- 3 files changed, 15 insertions(+), 16 deletions(-) create mode 100644 stylesheets/commons/MatchTable.scss diff --git a/stylesheets/Main.scss b/stylesheets/Main.scss index b5f39d63f02..91b2d1400b9 100644 --- a/stylesheets/Main.scss +++ b/stylesheets/Main.scss @@ -26,6 +26,7 @@ @use "commons/Infobox"; @use "commons/Mainpage"; @use "commons/Matchseries"; +@use "commons/MatchTable"; @use "commons/NavigationCard"; @use "commons/Miscellaneous"; @use "commons/BigMatch"; diff --git a/stylesheets/commons/MatchTable.scss b/stylesheets/commons/MatchTable.scss new file mode 100644 index 00000000000..97a238c7a8f --- /dev/null +++ b/stylesheets/commons/MatchTable.scss @@ -0,0 +1,14 @@ +/******************************************************************************* +Template(s): MatchTable +*******************************************************************************/ +.recent-matches-bg-win { + background-color: var( --table-green-background-color, #f0fff0 ) !important; +} + +.recent-matches-bg-tie { + background-color: var( --table-yellow-background-color, #f9f9c7 ) !important; +} + +.recent-matches-bg-lose { + background-color: var( --table-red-background-color, #f9f0f2 ) !important; +} diff --git a/stylesheets/commons/Miscellaneous.scss b/stylesheets/commons/Miscellaneous.scss index 6816da7dfc7..56301a8ecb6 100644 --- a/stylesheets/commons/Miscellaneous.scss +++ b/stylesheets/commons/Miscellaneous.scss @@ -2322,22 +2322,6 @@ Author(s): iMarbot color: #8b0000; } -/******************************************************************************* -Template(s): Template:Team matches table -Author(s): iMarbot via ??? -*******************************************************************************/ -.recent-matches-bg-win { - background-color: var( --table-green-background-color, #f0fff0 ) !important; -} - -.recent-matches-bg-tie { - background-color: var( --table-yellow-background-color, #f9f9c7 ) !important; -} - -.recent-matches-bg-lose { - background-color: var( --table-red-background-color, #f9f0f2 ) !important; -} - /******************************************************************************* Template(s): white-space as classes Author(s): iMarbot From 220cb3d9b06202efa84e0165d1fef849bae04420 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:22:57 +0900 Subject: [PATCH 12/52] use new table --- lua/wikis/commons/MatchTable.lua | 344 +++++++++++++++++++------------ 1 file changed, 217 insertions(+), 127 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index a5191a20c11..317925fd5b4 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -35,6 +35,8 @@ local OpponentDisplay = Lua.import('Module:OpponentDisplay/Custom') local Link = Lua.import('Module:Widget/Basic/Link') local MatchPageButton = Lua.import('Module:Widget/Match/PageButton') local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local WidgetUtil = Lua.import('Module:Widget/Util') local Condition = Lua.import('Module:Condition') local ConditionTree = Condition.Tree @@ -567,18 +569,6 @@ function MatchTable:buildDisplay() :node(self:_titleRow(self.config.title)) :node(self:headerRow()) - if Table.isEmpty(self.matches) then - local text = 'This ' .. (self.config.mode == Opponent.solo and Opponent.solo or Opponent.team) - .. ' has not played any matches yet.' - - return display:tag('tr') - :tag('td') - :attr('colspan', '100') - :css('font-style', 'italic') - :wikitext(text) - :allDone() - end - local currentYear Array.forEach(self.matches, function(match) local year = tonumber(match.date:sub(1, 4)) @@ -589,16 +579,16 @@ function MatchTable:buildDisplay() display:node(self:matchRow(match)) end) - if self.config.linkSubPage then - local pagename = self.title.text .. '/Matches' - display:tag('tr') - :tag('th') - :attr('colspan', 42) - :css('font-style', 'italic') - :wikitext('[[' .. pagename .. '|Extended list of matches]]') - end - - return display + return TableWidgets.Table{ + sortable = true, + columns = self:_buildColumnDefinitions(), + title = String.nilIfEmpty(self.config.title), + children = WidgetUtil.collect( + self:headerRow(), + TableWidgets.TableBody{children = self:buildBody()} + ), + footer = self:buildFooter() + } end ---@return Html @@ -624,79 +614,176 @@ function MatchTable:_titleRow(title) :done() end +---@private +---@return table[] +function MatchTable:_buildColumnDefinitions() + local config = self.config + return WidgetUtil.collect( + { + -- Date column + align = 'left', + sortType = 'number', + }, + config.showTier and {align = 'left'} or nil, + config.showType and {align = 'center'} or nil, + config.displayGameIcons and {align = 'center'} or nil, + config.showIcon and { + align = 'center', + unsortable = true, + } or nil, + { + -- Tournament column + align = 'left', + }, + config.showResult and WidgetUtil.collect( + config.showOpponent and {align = 'center'} or nil, + { + -- Score column + align = 'center', + }, + { + -- vs Opponent column + align = 'left' + } + ) or nil, + config.showVod and { + align = 'left', + unsortable = true, + } or nil, + config.showMatchPage and { + align = 'center', + unsortable = true, + } or nil + ) +end + ---@param year number? ----@return Html? +---@return Widget? function MatchTable:_yearRow(year) if not year then return end - return mw.html.create('tr') - :addClass('sortbottom') - :tag('td') - :attr('colspan', '100') - :addClass('match-table-year-header') - :wikitext(year) - :done() + return TableWidgets.Row{ + section = 'subhead', + classes = {'sortbottom'}, + css = {['font-weight'] = 'bold'}, + children = TableWidgets.CellHeader{ + align = 'center', + colspan = 100, + children = year + } + } end ---@return Html function MatchTable:headerRow() - local makeHeaderCell = function(text, width) - return mw.html.create('th'):css('max-width', width):node(text) + ---@param text string? + ---@return Widget + local makeHeaderCell = function(text) + return TableWidgets.CellHeader{children = text} end local config = self.config - return mw.html.create('tr') - :node(makeHeaderCell('Date', '100px')) - :node(config.showTier and makeHeaderCell('Tier', '70px') or nil) - :node(config.showType and makeHeaderCell('Type', '70px') or nil) - :node(config.displayGameIcons and makeHeaderCell(nil, '25px') or nil) - :node(config.showIcon and makeHeaderCell(nil, '25px'):addClass('unsortable') or nil) - :node(makeHeaderCell('Tournament')) - :node(config.showResult and config.showOpponent and makeHeaderCell('Participant', '120px') or nil) - :node(config.showResult and makeHeaderCell('Score', '68px'):addClass('unsortable') or nil) - :node(config.showResult and makeHeaderCell('vs. Opponent', '120px') or nil) - :node(config.showVod and makeHeaderCell('VOD(s)', '80px'):addClass('unsortable') or nil) - :node(config.showMatchPage and makeHeaderCell(''):addClass('unsortable') or nil) + return TableWidgets.TableHeader{children = { + TableWidgets.Row{children = WidgetUtil.collect( + makeHeaderCell('Date'), + config.showTier and makeHeaderCell('Tier') or nil, + config.showType and makeHeaderCell('Type') or nil, + config.displayGameIcons and makeHeaderCell() or nil, + config.showIcon and makeHeaderCell() or nil, + makeHeaderCell('Tournament'), + config.showResult and WidgetUtil.collect( + config.showOpponent and makeHeaderCell('Participant') or nil, + makeHeaderCell('Score'), + TableWidgets.CellHeader{ + align = 'center', + children = 'vs. Opponent' + } + ) or nil, + config.showVod and TableWidgets.CellHeader{ + align = 'center', + children = 'VOD(s)' + } or nil, + config.showMatchPage and makeHeaderCell() or nil + )} + }} +end + +---@return Widget[] +function MatchTable:buildBody() + if Table.isEmpty(self.matches) then + local text = 'This ' .. (self.config.mode == Opponent.solo and Opponent.solo or Opponent.team) + .. ' has not played any matches yet.' + return {TableWidgets.Row{ + css = {['font-style'] = 'italic'}, + children = TableWidgets.Cell{ + colspan = 100, + children = text, + } + }} + end + + ---@type Widget[] + local rows = {} + + local currentYear = math.huge + Array.forEach(self.matches, function(match) + local year = DateExt.getYearOf(match.date) + if self.config.showYearHeaders and year ~= currentYear then + currentYear = year + table.insert(rows, self:_yearRow(year)) + end + table.insert(rows, self:matchRow(match)) + end) + + return rows +end + +---@return Widget? +function MatchTable:buildFooter() + if not self.config.linkSubPage then + return + end + return Link{ + link = self.title.text .. '/Matches', + children = 'Extended list of matches' + } end ---@param match MatchTableMatch ----@return Html? +---@return Widget function MatchTable:matchRow(match) - return mw.html.create('tr') - :addClass(self:_getBackgroundClass(match.result.winner)) - :node(self:_displayDate(match)) - :node(self:_displayTier(match)) - :node(self:_displayType(match)) - :node(self:_displayGameIcon(match)) - :node(self:_displayIcon(match)) - :node(self:_displayTournament(match)) - :node(self:_displayMatch(match)) - :node(self:_displayVods(match)) - :node(self:_displayMatchPage(match)) + return TableWidgets.Row{ + classes = {self:_getBackgroundClass(match.result.winner)}, + children = WidgetUtil.collect( + self:_displayDate(match), + self:_displayTier(match), + self:_displayType(match), + self:_displayGameIcon(match), + self:_displayIcon(match), + self:_displayTournament(match), + self:_displayMatch(match), + self:_displayVods(match), + self:_displayMatchPage(match) + ) + } end ---@param match MatchTableMatch ----@return Html +---@return Widget function MatchTable:_displayDate(match) - local cell = mw.html.create('td') - :css('text-align', 'left') - :css('min-width', '5rem') - :attr('data-sort-value', match.timestamp) - - if match.timestamp == DateExt.defaultTimestamp then - return cell - end - - return cell:node(Countdown.create{ - finished = match.finished, - date = DateExt.toCountdownArg(match.timestamp, match.timezoneId, match.dateIsExact), - rawdatetime = true, - format = self.config.dateFormat - } or nil) + return TableWidgets.Cell{ + attributes = {['data-sort-value'] = match.timestamp}, + children = not DateExt.isDefaultTimestamp(match.timestamp) and Countdown.create{ + finished = match.finished, + date = DateExt.toCountdownArg(match.timestamp, match.timezoneId, match.dateIsExact), + rawdatetime = true, + format = self.config.dateFormat + } or nil + } end ---@param match MatchTableMatch ----@return Html? +---@return Widget? function MatchTable:_displayTier(match) if not self.config.showTier then return end @@ -705,60 +792,64 @@ function MatchTable:_displayTier(match) options.onlyTierTypeIfBoth = true if not Tier.isValid(tier, tierType) then - return mw.html.create('td') - :attr('data-sort-value', INVALID_TIER_SORT) - :wikitext(INVALID_TIER_DISPLAY) + return TableWidgets.Cell{ + attributes = {['data-sort-value'] = INVALID_TIER_SORT}, + children = INVALID_TIER_DISPLAY + } end - return mw.html.create('td') - :attr('data-sort-value', Tier.toSortValue(tier, tierType)) - :wikitext(Tier.display(tier, tierType, options)) + return TableWidgets.Cell{ + attributes = {['data-sort-value'] = Tier.toSortValue(tier, tierType)}, + children = Tier.display(tier, tierType, options) + } end ---@param match MatchTableMatch ----@return Html? +---@return Widget? function MatchTable:_displayType(match) if not self.config.showType then return end - return mw.html.create('td') - :wikitext(match.type and mw.getContentLanguage():ucfirst(match.type) or nil) + return TableWidgets.Cell{ + children = match.type and String.upperCaseFirst(match.type) or nil + } end ---@param match MatchTableMatch ----@return Html? +---@return Widget? function MatchTable:_displayGameIcon(match) if not self.config.displayGameIcons then return end - return mw.html.create('td') - :node(Game.icon{game = match.game}) + return TableWidgets.Cell{ + children = Game.icon{game = match.game} + } end ---@param match MatchTableMatch ----@return Html? +---@return Widget? function MatchTable:_displayIcon(match) if not self.config.showIcon then return end - return mw.html.create('td') - :node(LeagueIcon.display{ + return TableWidgets.Cell{ + children = LeagueIcon.display{ icon = match.icon, iconDark = match.iconDark, link = match.pageName, name = match.displayName, options = {noTemplate = true}, - }) + } + } end ---@param match MatchTableMatch ---@return Widget function MatchTable:_displayTournament(match) - return HtmlWidgets.Td{ - css = {['text-align'] = 'left'}, + return TableWidgets.Cell{ children = Link{children = match.displayName, link = match.pageName} } end ---@param match MatchTableMatch ----@return Html? +---@return Widget|Widget[]? function MatchTable:_displayMatch(match) if not self.config.showResult then return @@ -766,36 +857,36 @@ function MatchTable:_displayMatch(match) return self:nonStandardMatch(match) end - return mw.html.create() - :node(self.config.showOpponent and self:_displayOpponent(match.result.opponent, true) or nil) - :node(self:_displayScore(match)) - :node(self:_displayOpponent(match.result.vs):css('text-align', 'left')) + return WidgetUtil.collect( + self.config.showOpponent and self:_displayOpponent(match.result.opponent, true) or nil, + self:_displayScore(match), + self:_displayOpponent(match.result.vs) + ) end ---overwritable for wikis that have BR/FFA matches ---@param match MatchTableMatch ----@return Html +---@return Widget function MatchTable:nonStandardMatch(match) - return mw.html.create('td') - :attr('colspan', self.config.showOpponent and 3 or 2) - :wikitext('') + return TableWidgets.Cell{ + colspan = self.config.showOpponent and 3 or 2, + children = '', + } end ---@param opponent standardOpponent ---@param flipped boolean? ----@return Html +---@return Widget function MatchTable:_displayOpponent(opponent, flipped) - local cell = mw.html.create('td') - if Logic.isEmpty(opponent) then return cell:wikitext('Unknown') end - - return cell - :node(OpponentDisplay.BlockOpponent{ + return TableWidgets.Cell{ + attributes = {['data-sort-value'] = Opponent.toName(opponent)}, + children = OpponentDisplay.BlockOpponent{ opponent = opponent, flip = flipped, overflow = 'wrap', teamStyle = self.config.teamStyle, - }) - :attr('data-sort-value', opponent.name) + } + } end ---@param match MatchTableMatch @@ -808,7 +899,7 @@ function MatchTable:_displayScore(match) ---@param opponent standardOpponent ---@param gameOpponents table[] - ---@return Html|string + ---@return string|Widget local toScore = function(opponent, gameOpponents) if Table.isEmpty(opponent) or not opponent.status then return 'Unkn' end local score = OpponentDisplay.InlineScore(opponent) @@ -820,16 +911,17 @@ function MatchTable:_displayScore(match) status = game1Opponent.status end - return mw.html.create(tonumber(opponent.placement) == 1 and 'b' or nil) - :wikitext(status == SCORE_STATUS and (score or '–') or status) + return HtmlWidgets.Span{ + css = {['font-weight'] = tonumber(opponent.placement) == 1 and 'bold' or nil}, + children = status == SCORE_STATUS and (score or '–') or status, + } end - return mw.html.create('td') - :addClass('match-table-score') - :css('white-space', 'nowrap') - :node(toScore(result.opponent, result.gameOpponents)) - :node(bestof1Score and BO1_SCORE_CONCAT or SCORE_CONCAT) - :node(toScore(result.vs, result.gameVsOpponents)) + return TableWidgets.Cell{children = { + toScore(result.opponent, result.gameOpponents), + bestof1Score and BO1_SCORE_CONCAT or SCORE_CONCAT, + toScore(result.vs, result.gameVsOpponents) + }} end ---@param match MatchTableMatch @@ -837,15 +929,11 @@ end function MatchTable:_displayVods(match) if not self.config.showVod then return end - local vodsNode = mw.html.create('td'):css('text-align', 'left') - Array.forEach(match.vods, function(vod, vodIndex) - if vodIndex ~= 1 then - vodsNode:node(' ') - end - vodsNode:node(VodLink.display{vod = vod.link, gamenum = vod.index ~= 0 and vod.index or nil}) - end) - - return vodsNode + return TableWidgets.Cell{ + children = Array.interleave(Array.map(match.vods, function (vod) + return VodLink.display{vod = vod.link, gamenum = vod.index ~= 0 and vod.index or nil} + end), ' ') + } end ---@param match MatchTableMatch @@ -853,7 +941,9 @@ end function MatchTable:_displayMatchPage(match) if not self.config.showMatchPage then return end - return mw.html.create('td'):node(MatchPageButton{match = match, buttonText = self.config.matchPageButtonText}) + return TableWidgets.Cell{ + children = MatchPageButton{match = match, buttonText = self.config.matchPageButtonText} + } end ---@param winner any From 2377374452a6a73b0c1683ad1b3bea4c458ec4dc Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:24:51 +0900 Subject: [PATCH 13/52] use formatPercentage --- lua/wikis/commons/MatchTable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 317925fd5b4..2c8d4aa122a 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -975,11 +975,11 @@ function MatchTable:displayStats() data.l .. 'L' ), ' : ') - local percentage = Math.round((data.w + 0.5 * data.d) / sum, 4) * 100 + local percentage = Math.formatPercentage((data.w + 0.5 * data.d) / sum, 2) local parts = { scoreText, - '(' .. percentage .. '%)', + '(' .. percentage .. ')', 'in', statsType, } From ee69938d1d05a048b8e9805d8b11a5c37303f6e6 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:25:38 +0900 Subject: [PATCH 14/52] isDefaultTimestamp --- lua/wikis/commons/MatchTable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 2c8d4aa122a..beedb2103ed 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -988,9 +988,9 @@ function MatchTable:displayStats() end local makeStatsTitle = function() - if startTimeStamp == DateExt.defaultTimestamp and endTimeStamp == DateExt.defaultTimestamp then + if DateExt.isDefaultTimestamp(startTimeStamp) and DateExt.isDefaultTimestamp(endTimeStamp) then return 'For all matches:' - elseif startTimeStamp == DateExt.defaultTimestamp then + elseif DateExt.isDefaultTimestamp(startTimeStamp) then return 'For all matches before '.. DateExt.formatTimestamp('M d, Y', endTimeStamp) .. ':' end From 1273755d5adbc24bed380f187d4f8b7f8681fc3b Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:36:31 +0900 Subject: [PATCH 15/52] clean up stats --- lua/wikis/commons/MatchTable.lua | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index beedb2103ed..f798566815f 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -955,7 +955,7 @@ function MatchTable:_getBackgroundClass(winner) nil end ----@return Html? +---@return Widget? function MatchTable:displayStats() if not self.config.showStats or Table.isEmpty(self.matches) then return end @@ -999,9 +999,10 @@ function MatchTable:displayStats() return 'For matches between ' .. startDate .. ' and ' .. endDate .. ':' end - local titleNode = mw.html.create('div') - :css('font-weight', 'bold') - :wikitext(makeStatsTitle()) + local titleNode = HtmlWidgets.Div{ + css = {['font-weight'] = 'bold'}, + children = makeStatsTitle(), + } local stats = Array.append({}, self.config.showOnlyGameStats and '' or displayScores(self.stats.matches, 'matches'), @@ -1009,12 +1010,10 @@ function MatchTable:displayStats() self.config.showOnlyGameStats and '' or displayScores(self.stats.rounds, 'rounds') ) - return mw.html.create('div') - :node(titleNode) - :tag('div') - :wikitext(table.concat(stats, self.config.showOnlyGameStats and '' or ' and ')) - :wikitext() - :done() + return HtmlWidgets.Div{children = { + titleNode, + HtmlWidgets.Div{children = Array.interleave(stats, self.config.showOnlyGameStats and '' or ' and ')} + }} end return MatchTable From 2d3b27e8aae7936b9c2551c8e6e898cd7f4bc94b Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:38:13 +0900 Subject: [PATCH 16/52] clean up build --- lua/wikis/commons/MatchTable.lua | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index f798566815f..e4e7ec4e648 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -561,7 +561,7 @@ function MatchTable:statsFromMatches() } end ----@return Html +---@return Widget function MatchTable:buildDisplay() local display = mw.html.create('table') :addClass('wikitable wikitable-striped sortable') @@ -591,16 +591,12 @@ function MatchTable:buildDisplay() } end ----@return Html +---@return Widget function MatchTable:build() - local wrappedTableNode = mw.html.create('div') - :addClass('match-table-wrapper') - :addClass('table-responsive') - :node(self:buildDisplay()) - - return mw.html.create('div') - :node(self:displayStats()) - :node(wrappedTableNode) + return HtmlWidgets.Fragment{children = WidgetUtil.collect( + self:displayStats(), + self:buildDisplay() + )} end ---@param title string From f1a5c368cd29fffec9801145d1c91ca217a1fa9a Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:41:39 +0900 Subject: [PATCH 17/52] update custom --- lua/wikis/commons/MatchTable/Custom.lua | 2 +- lua/wikis/counterstrike/MatchTable/Custom.lua | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lua/wikis/commons/MatchTable/Custom.lua b/lua/wikis/commons/MatchTable/Custom.lua index 9e38bd4a7d2..787c8a33d2e 100644 --- a/lua/wikis/commons/MatchTable/Custom.lua +++ b/lua/wikis/commons/MatchTable/Custom.lua @@ -14,7 +14,7 @@ local MatchTable = Lua.import('Module:MatchTable') local CustomMatchTable = {} ---@param args table ----@return Html +---@return Widget function CustomMatchTable.results(args) return MatchTable(args):readConfig():query():build() end diff --git a/lua/wikis/counterstrike/MatchTable/Custom.lua b/lua/wikis/counterstrike/MatchTable/Custom.lua index 72216c89bac..94922c169fe 100644 --- a/lua/wikis/counterstrike/MatchTable/Custom.lua +++ b/lua/wikis/counterstrike/MatchTable/Custom.lua @@ -13,27 +13,28 @@ local Tier = Lua.import('Module:Tier/Custom') local MatchTable = Lua.import('Module:MatchTable') +local TableWidgets = Lua.import('Module:Widget/Table2/All') + local INVALID_TIER_DISPLAY = 'Undefined' local INVALID_TIER_SORT = 'ZZ' -local CustomMatchTable = {} +---@class CounterstrikeMatchTable: MatchTable +---@operator call(table): CounterstrikeMatchTable +local CustomMatchTable = Class.new(MatchTable) ---@param args table ----@return Html +---@return Widget function CustomMatchTable.results(args) args.showRoundStats = Logic.nilOr(Logic.readBoolOrNil(args.showRoundStats), true) args.gameIcons = Logic.nilOr(Logic.readBoolOrNil(args.gameIcons), true) args.vod = Logic.nilOr(Logic.readBoolOrNil(args.vod), true) args.showType = Logic.nilOr(Logic.readBoolOrNil(args.showType), true) - local matchtable = MatchTable(args) - matchtable._displayTier = CustomMatchTable._displayTier - - return matchtable:readConfig():query():build() + return CustomMatchTable(args):readConfig():query():build() end ---@param match MatchTableMatch ----@return Html? +---@return Widget? function CustomMatchTable:_displayTier(match) if not self.config.showTier then return end @@ -42,14 +43,16 @@ function CustomMatchTable:_displayTier(match) options.onlyDisplayPrioritized = true if not Tier.isValid(tier, tierType) then - return mw.html.create('td') - :attr('data-sort-value', INVALID_TIER_DISPLAY) - :wikitext(INVALID_TIER_SORT) + return TableWidgets.Cell{ + attributes = {['data-sort-value'] = INVALID_TIER_SORT}, + children = INVALID_TIER_DISPLAY + } end - return mw.html.create('td') - :attr('data-sort-value', Tier.toSortValue(tier, tierType)) - :wikitext(Tier.display(tier, tierType, options)) + return TableWidgets.Cell{ + attributes = {['data-sort-value'] = Tier.toSortValue(tier, tierType)}, + children = Tier.display(tier, tierType, options) + } end return Class.export(CustomMatchTable, {exports = {'results'}}) From 5e967557421e55d97cc7f8fafa0f84f1bf02375b Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:45:05 +0900 Subject: [PATCH 18/52] adjust access modifier --- lua/wikis/commons/GameTable.lua | 2 +- lua/wikis/commons/GameTable/Character.lua | 2 +- lua/wikis/commons/MatchTable.lua | 5 +++-- lua/wikis/counterstrike/MatchTable/Custom.lua | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lua/wikis/commons/GameTable.lua b/lua/wikis/commons/GameTable.lua index e7965d09614..406c1e6de94 100644 --- a/lua/wikis/commons/GameTable.lua +++ b/lua/wikis/commons/GameTable.lua @@ -126,7 +126,7 @@ function GameTable:gameRow(match, game) return mw.html.create('tr') :addClass(self:_getBackgroundClass(winner)) :node(self:_displayDate(match)) - :node(self:_displayTier(match)) + :node(self:displayTier(match)) :node(self:_displayType(match)) :node(self:_displayGameIconForGame(game)) :node(self:_displayIcon(match)) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index f09268f9f78..f1bac3241a8 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -422,7 +422,7 @@ function CharacterGameTable:gameRow(match, game) return mw.html.create('tr') :addClass(self:_getBackgroundClass(winner)) :node(self:_displayDate(match)) - :node(self:_displayTier(match)) + :node(self:displayTier(match)) :node(self:_displayType(match)) :node(self:_displayGameIconForGame(game)) :node(self:_displayIcon(match)) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index e4e7ec4e648..d5e8f2a8a4f 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -752,7 +752,7 @@ function MatchTable:matchRow(match) classes = {self:_getBackgroundClass(match.result.winner)}, children = WidgetUtil.collect( self:_displayDate(match), - self:_displayTier(match), + self:displayTier(match), self:_displayType(match), self:_displayGameIcon(match), self:_displayIcon(match), @@ -778,9 +778,10 @@ function MatchTable:_displayDate(match) } end +---@protected ---@param match MatchTableMatch ---@return Widget? -function MatchTable:_displayTier(match) +function MatchTable:displayTier(match) if not self.config.showTier then return end local tier, tierType, options = Tier.parseFromQueryData(match) diff --git a/lua/wikis/counterstrike/MatchTable/Custom.lua b/lua/wikis/counterstrike/MatchTable/Custom.lua index 94922c169fe..66fe317e612 100644 --- a/lua/wikis/counterstrike/MatchTable/Custom.lua +++ b/lua/wikis/counterstrike/MatchTable/Custom.lua @@ -33,9 +33,10 @@ function CustomMatchTable.results(args) return CustomMatchTable(args):readConfig():query():build() end +---@protected ---@param match MatchTableMatch ---@return Widget? -function CustomMatchTable:_displayTier(match) +function CustomMatchTable:displayTier(match) if not self.config.showTier then return end local tier, tierType, options = Tier.parseFromQueryData(match) From b416f16519d9b8bdb31f79d578ef92ff42f62568 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:48:19 +0900 Subject: [PATCH 19/52] use html entity over code --- lua/wikis/commons/GameTable.lua | 8 ++------ lua/wikis/commons/GameTable/Character.lua | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lua/wikis/commons/GameTable.lua b/lua/wikis/commons/GameTable.lua index 406c1e6de94..dd37889842d 100644 --- a/lua/wikis/commons/GameTable.lua +++ b/lua/wikis/commons/GameTable.lua @@ -17,10 +17,7 @@ local VodLink = Lua.import('Module:VodLink') local MatchTable = Lua.import('Module:MatchTable') local NOT_PLAYED = 'notplayed' -local SCORE_CONCAT = ' : ' - ----@class GameTableMatch: MatchTableMatch ----@field games match2game[] +local SCORE_CONCAT = ' : ' ---@class GameTable: MatchTable ---@field countGames number @@ -40,11 +37,10 @@ function GameTable:gameFromRecord(game) end ---@param record table ----@return GameTableMatch? +---@return MatchTableMatch? function GameTable:matchFromRecord(record) if self.countGames == self.config.limit then return nil end local matchRecord = MatchTable.matchFromRecord(self, record) - ---@cast matchRecord GameTableMatch if Logic.isEmpty(record.match2games) then return nil end diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index f1bac3241a8..e0cedf79499 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -26,7 +26,7 @@ local ColumnName = Condition.ColumnName local DRAW_WINNER = 0 local CHARACTER_MODE = 'character' -local SCORE_CONCAT = ' : ' +local SCORE_CONCAT = ' : ' ---@class CharacterGameTableConfig: MatchTableConfig ---@field showGameWithoutCharacters boolean From c2d09d42417fa0ad6145e8925851ed609fc5c140 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 18:33:48 +0900 Subject: [PATCH 20/52] slice buildRows --- lua/wikis/commons/MatchTable.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index d5e8f2a8a4f..9c83a342e29 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -718,6 +718,11 @@ function MatchTable:buildBody() }} end + return self:buildRows() +end + +---@return Widget[] +function MatchTable:buildRows() ---@type Widget[] local rows = {} From 739c5ee38d684cd005fd7c0452eb8280750c8c21 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 19:28:04 +0900 Subject: [PATCH 21/52] clean up GameTable --- lua/wikis/commons/GameTable.lua | 168 +++++++++++++++++-------------- lua/wikis/commons/MatchTable.lua | 4 + 2 files changed, 98 insertions(+), 74 deletions(-) diff --git a/lua/wikis/commons/GameTable.lua b/lua/wikis/commons/GameTable.lua index dd37889842d..afaf40aa660 100644 --- a/lua/wikis/commons/GameTable.lua +++ b/lua/wikis/commons/GameTable.lua @@ -9,6 +9,7 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') local Class = Lua.import('Module:Class') +local DateExt = Lua.import('Module:Date/Ext') local Game = Lua.import('Module:Game') local Logic = Lua.import('Module:Logic') local Operator = Lua.import('Module:Operator') @@ -16,90 +17,94 @@ local VodLink = Lua.import('Module:VodLink') local MatchTable = Lua.import('Module:MatchTable') +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local WidgetUtil = Lua.import('Module:Widget/Util') + local NOT_PLAYED = 'notplayed' local SCORE_CONCAT = ' : ' ---@class GameTable: MatchTable ----@field countGames number +---@operator call(table): GameTable +---@field countGames integer local GameTable = Class.new(MatchTable, function (self) self.countGames = 0 end) ----@param game match2game ----@return match2game? -function GameTable:gameFromRecord(game) - if self.countGames == self.config.limit then return nil end - if game.status == NOT_PLAYED or Logic.isEmpty(game.winner) then - return nil - end - - return game -end - ----@param record table +---@param record match2 ---@return MatchTableMatch? function GameTable:matchFromRecord(record) - if self.countGames == self.config.limit then return nil end + if self.countGames >= self.config.limit then return nil end local matchRecord = MatchTable.matchFromRecord(self, record) - if Logic.isEmpty(record.match2games) then + if not matchRecord then + return + elseif Logic.isEmpty(record.match2games) then return nil end - matchRecord.games = {} - --order games from last played to first - Array.forEach(Array.reverse(record.match2games), function (game) - local gameRecord = self:gameFromRecord(game) - if gameRecord then self.countGames = self.countGames + 1 end - table.insert(matchRecord.games, gameRecord) - end) + ---@param game MatchGroupUtilGame + ---@return boolean + local function gameIsFinished(game) + return game.status ~= NOT_PLAYED and Logic.isNotEmpty(game.winner) + end + + matchRecord.games = Array.filter(matchRecord.games, gameIsFinished) + + self.countGames = self.countGames + #matchRecord.games return matchRecord end ---@param vod string? ----@return Html? +---@return Widget? function GameTable:_displayGameVod(vod) - if not self.config.showVod then return end - - local vodNode = mw.html.create('td') - if Logic.isEmpty(vod) then - return vodNode:wikitext('') + if not self.config.showVod then + return + elseif Logic.isEmpty(vod) then + return TableWidgets.Cell{} end ---@cast vod -nil - return vodNode:node(VodLink.display{vod = vod}) + return TableWidgets.Cell{children = VodLink.display{vod = vod}} end ---@param result MatchTableMatchResult ----@param game match2game +---@param game MatchGroupUtilGame ---@return Html? function GameTable:_displayGameScore(result, game) local scores = Array.map(game.opponents, Operator.property('score')) - local toScore = function(opponentRecord) - local isWinner = opponentRecord.id == tonumber(game.winner) - local score = scores[opponentRecord.id] or (isWinner and 1) or 0 - return mw.html.create(isWinner and 'b' or nil) - :wikitext(score) + local indexes = result.flipped and {2, 1} or {1, 2} + + ---@param opponentIndex integer + ---@return Widget + local toScore = function(opponentIndex) + local isWinner = opponentIndex == tonumber(game.winner) + local score = scores[opponentIndex] or (isWinner and 1) or 0 + return HtmlWidgets.Span{ + css = {['font-weight'] = isWinner and 'bold' or nil}, + children = score + } end - return mw.html.create('td') - :addClass('match-table-score') - :node(toScore(result.opponent)) - :node(SCORE_CONCAT) - :node(toScore(result.vs)) + return TableWidgets.Cell{children = { + toScore(indexes[1]), + SCORE_CONCAT, + toScore(indexes[2]), + }} end ----@param game match2game +---@param game MatchGroupUtilGame ---@return Html? function GameTable:_displayGameIconForGame(game) if not self.config.displayGameIcons then return end - return mw.html.create('td') - :node(Game.icon{game = game.game}) + return TableWidgets.Cell{ + children = Game.icon{game = game.game} + } end ----@param match GameTableMatch ----@param game match2game ----@return Html? +---@param match MatchTableMatch +---@param game MatchGroupUtilGame +---@return Widget|Widget[]? function GameTable:displayGame(match, game) if not self.config.showResult then return @@ -107,41 +112,56 @@ function GameTable:displayGame(match, game) return self:nonStandardMatch(match) end - return mw.html.create() - :node(self.config.showOpponent and self:_displayOpponent(match.result.opponent, true) or nil) - :node(self:_displayGameScore(match.result, game)) - :node(self:_displayOpponent(match.result.vs):css('text-align', 'left')) + return WidgetUtil.collect( + self.config.showOpponent and self:_displayOpponent(match.result.opponent, true) or nil, + self:_displayGameScore(match.result, game), + self:_displayOpponent(match.result.vs) + ) end ----@param match GameTableMatch ----@param game match2game ----@return Html? +---@param match MatchTableMatch +---@param game MatchGroupUtilGame +---@return Widget function GameTable:gameRow(match, game) - local winner = match.result.opponent.id == tonumber(game.winner) and 1 or 2 - - return mw.html.create('tr') - :addClass(self:_getBackgroundClass(winner)) - :node(self:_displayDate(match)) - :node(self:displayTier(match)) - :node(self:_displayType(match)) - :node(self:_displayGameIconForGame(game)) - :node(self:_displayIcon(match)) - :node(self:_displayTournament(match)) - :node(self:displayGame(match, game)) - :node(self:_displayGameVod(game.vod)) - :node(self:_displayMatchPage(match)) + local indexes = match.result.flipped and {2, 1} or {1, 2} + local winner = game.winner == indexes[1] + + return TableWidgets.Row{ + classes = {self:_getBackgroundClass(winner)}, + children = WidgetUtil.collect( + self:_displayDate(match), + self:displayTier(match), + self:_displayType(match), + self:_displayGameIconForGame(game), + self:_displayIcon(match), + self:_displayTournament(match), + self:displayGame(match, game), + self:_displayGameVod(game.vod), + self:_displayMatchPage(match) + ) + } end ----@param match GameTableMatch ----@return Html? -function GameTable:matchRow(match) - local display = mw.html.create() - - Array.forEach(match.games, function(game) - display:node(self:gameRow(match, game)) +---@return Widget[] +function GameTable:buildRows() + ---@type Widget[] + local rows = {} + + local currentYear = math.huge + Array.forEach(self.matches, function(match) + local year = DateExt.getYearOf(match.date) + if self.config.showYearHeaders and year ~= currentYear then + currentYear = year + table.insert(rows, self:_yearRow(year)) + end + Array.extendWith(rows, Array.reverse( + Array.map(match.games, function (game) + self:gameRow(match, game) + end) + )) end) - return display + return rows end return GameTable diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 9c83a342e29..c5114fea4d0 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -96,6 +96,7 @@ local SECONDS_ONE_DAY = 3600 * 24 ---@field vs standardOpponent ---@field gameVsOpponents table[] ---@field winner number? +---@field flipped boolean ---@field countGames boolean ---@field countRounds boolean @@ -478,11 +479,13 @@ function MatchTable:resultFromRecord(record) end local winner = record.winner + local flipped = false local indexes if foundInAlias(record.opponents[1]) then indexes = {1, 2} elseif foundInAlias(record.opponents[2]) then indexes = {2, 1} + flipped = true winner = winner == 2 and 1 or winner == 1 and 2 or winner else mw.ext.TeamLiquidIntegration.add_category('MatchesTables with invalid matches') @@ -497,6 +500,7 @@ function MatchTable:resultFromRecord(record) opponent = record.opponents[indexes[1]], vs = record.opponents[indexes[2]], winner = winner, + flipped = flipped, countGames = countGames, countRounds = countRounds, gameOpponents = Array.map(gameOpponents, Operator.property(indexes[1])), From be4ac6d3e1a9fb3e34f1549eea01f2fb475a3b73 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 19:29:07 +0900 Subject: [PATCH 22/52] type anno --- lua/wikis/commons/GameTable/Character.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index e0cedf79499..127ca255103 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -45,6 +45,7 @@ local SCORE_CONCAT = ' : ' ---@field pickedByplayer number? ---@class CharacterGameTable: GameTable +---@operator call(table): CharacterGameTable ---@field character string ---@field isCharacterTable boolean ---@field isPickedByRequired boolean From 3a9a91ad7468d25035ac1ea6e7f9942b877856c4 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 19:30:43 +0900 Subject: [PATCH 23/52] use isMain --- lua/wikis/commons/GameTable/Character.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 127ca255103..b2138ab1d18 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -12,6 +12,7 @@ local Array = Lua.import('Module:Array') local CharacterIcon = Lua.import('Module:CharacterIcon') local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') +local Namespace = Lua.import('Module:Namespace') local Operator = Lua.import('Module:Operator') local Table = Lua.import('Module:Table') @@ -76,7 +77,7 @@ function CharacterGameTable:readCharacter() if Logic.isNotEmpty(self.args.character) then self.character = self.args.character else - assert(self.title.namespace == 0, 'Lua.importd character= argument') + assert(Namespace.isMain(self.title), 'Lua.importd character= argument') self.character = self.title.rootText end From c1d4461d53b9f0709c047e9e9c0b14816c5a2c18 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 22:58:44 +0900 Subject: [PATCH 24/52] rewrite charactergametable --- lua/wikis/commons/GameTable.lua | 16 +- lua/wikis/commons/GameTable/Character.lua | 287 +++++++++++++--------- lua/wikis/commons/MatchTable.lua | 6 +- 3 files changed, 186 insertions(+), 123 deletions(-) diff --git a/lua/wikis/commons/GameTable.lua b/lua/wikis/commons/GameTable.lua index afaf40aa660..2e6fc5bf3f2 100644 --- a/lua/wikis/commons/GameTable.lua +++ b/lua/wikis/commons/GameTable.lua @@ -42,19 +42,21 @@ function GameTable:matchFromRecord(record) return nil end - ---@param game MatchGroupUtilGame - ---@return boolean - local function gameIsFinished(game) - return game.status ~= NOT_PLAYED and Logic.isNotEmpty(game.winner) - end - - matchRecord.games = Array.filter(matchRecord.games, gameIsFinished) + matchRecord.games = Array.filter(matchRecord.games, function (game) + return self:filterGame(game) + end) self.countGames = self.countGames + #matchRecord.games return matchRecord end +---@param game MatchGroupUtilGame +---@return boolean +function GameTable:filterGame(game) + return game.status ~= NOT_PLAYED and Logic.isNotEmpty(game.winner) +end + ---@param vod string? ---@return Widget? function GameTable:_displayGameVod(vod) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index b2138ab1d18..2a14f79ff5f 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -25,6 +25,11 @@ local Comparator = Condition.Comparator local BooleanOperator = Condition.BooleanOperator local ColumnName = Condition.ColumnName +local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local MatchSummaryCharacters = Lua.import('Module:Widget/Match/Summary/Characters') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local WidgetUtil = Lua.import('Module:Widget/Util') + local DRAW_WINNER = 0 local CHARACTER_MODE = 'character' local SCORE_CONCAT = ' : ' @@ -39,18 +44,22 @@ local SCORE_CONCAT = ' : ' ---@field iconSize string ---@field iconSeparator string ----@class CharacterGameTableGame: match2game +---@class CharacterGameTableGame: MatchGroupUtilGame ---@field picks string[][] ---@field bans string[][]? ---@field pickedBy number? ---@field pickedByplayer number? +---@class CharacterGameTableMatch: MatchTableMatch +---@field games CharacterGameTableGame[] + ---@class CharacterGameTable: GameTable ---@operator call(table): CharacterGameTable ---@field character string ---@field isCharacterTable boolean ---@field isPickedByRequired boolean ---@field config CharacterGameTableConfig +---@field matches CharacterGameTableMatch[] local CharacterGameTable = Class.new(GameTable, function (self) self.isCharacterTable = self.args.tableMode == CHARACTER_MODE self.isPickedByRequired = self.isCharacterTable @@ -221,26 +230,21 @@ function CharacterGameTable:getCharacterPick(game) return findCharacter(1) or findCharacter(2) end ----@param game match2game ----@return match2game? -function CharacterGameTable:gameFromRecord(game) - local gameRecord = GameTable.gameFromRecord(self, game) - if not gameRecord then - return nil - end - - ---@cast gameRecord CharacterGameTableGame - gameRecord.picks = self:getCharacters(gameRecord, self.config.numPicks, self.getCharacterKey) - gameRecord.bans = self.config.showBans and - self:getCharacters(gameRecord, self.config.numBans,self.getCharacterBanKey) or nil - gameRecord.pickedBy = self.isPickedByRequired and self:getCharacterPick(gameRecord) or nil +---@param game MatchGroupUtilGame +---@return boolean +function CharacterGameTable:filterGame(game) + ---@cast game CharacterGameTableGame + game.picks = self:getCharacters(game, self.config.numPicks, self.getCharacterKey) + game.bans = self.config.showBans and + self:getCharacters(game, self.config.numBans,self.getCharacterBanKey) or nil + game.pickedBy = self.isPickedByRequired and self:getCharacterPick(game) or nil if self.isPickedByRequired then - return Logic.isNotEmpty(gameRecord.pickedBy) and gameRecord or nil + return Logic.isNotEmpty(game.pickedBy) end - local foundPicks = Table.isNotEmpty(gameRecord.picks[1]) or Table.isNotEmpty(gameRecord.picks[2]) - return (foundPicks or self.config.showGameWithoutCharacters) and gameRecord or nil + local foundPicks = Table.isNotEmpty(game.picks[1]) or Table.isNotEmpty(game.picks[2]) + return foundPicks or self.config.showGameWithoutCharacters end ---@param record table @@ -259,7 +263,6 @@ function CharacterGameTable:statsFromMatches() local totalGames = {w = 0, d = 0, l = 0} Array.forEach(self.matches, function(match) - ---@cast match GameTableMatch Array.forEach(match.games, function (game, index) local winner = tonumber(game.winner) @@ -278,71 +281,125 @@ function CharacterGameTable:statsFromMatches() } end ----@return Html -function CharacterGameTable:headerRow() - local makeHeaderCell = function(text, width) - return mw.html.create('th'):css('max-width', width):node(text) - end - +---@protected +---@return table[] +function CharacterGameTable:buildColumnDefinitions() local config = self.config - - local nodes = Array.append({}, - makeHeaderCell('Date', '100px'), - config.showTier and makeHeaderCell('Tier', '70px') or nil, - config.showType and makeHeaderCell('Type', '70px') or nil, - config.displayGameIcons and makeHeaderCell(nil, '25px') or nil, - config.showIcon and makeHeaderCell(nil, '25px'):addClass('unsortable') or nil, - makeHeaderCell('Tournament') + local isCharTable = self.isCharacterTable + return WidgetUtil.collect( + { + -- Date column + align = 'left', + sortType = 'number', + }, + config.showTier and {align = 'left'} or nil, + config.showType and {align = 'center'} or nil, + config.displayGameIcons and {align = 'center'} or nil, + config.showIcon and { + align = 'center', + unsortable = true, + } or nil, + { + -- Tournament column + align = 'left', + }, + config.showResult and WidgetUtil.collect( + not isCharTable and {align = 'center'} or nil, + { + align = 'center', + unsortable = true, + }, + config.showBans and { + align = 'center', + unsortable = true + } or nil, + isCharTable and { + {align = 'center'}, + {align = 'center'}, + {align = 'center'}, + } or nil, + { + align = 'center', + unsortable = true, + }, + config.showBans and { + align = 'center', + unsortable = true + } or nil + ) or nil, + config.showLength and { + align = 'left', + unsortable = true, + } or nil, + config.showVod and { + align = 'left', + unsortable = true, + } or nil, + config.showMatchPage and { + align = 'center', + unsortable = true, + } or nil ) +end - if config.showResult then - local isCharTable = self.isCharacterTable - nodes = Array.appendWith(nodes, - not isCharTable and makeHeaderCell('vs.', '80px') or nil, - makeHeaderCell('Picks'):addClass('unsortable'), - config.showBans and makeHeaderCell('Bans'):addClass('unsortable') or nil, - isCharTable and makeHeaderCell(nil, '80px') or nil, - isCharTable and makeHeaderCell('Score') or nil, - isCharTable and makeHeaderCell(nil, '80px') or nil, - makeHeaderCell('vs. Picks'):addClass('unsortable'), - config.showBans and makeHeaderCell('vs. Bans'):addClass('unsortable') or nil - ) +---@return Widget +function CharacterGameTable:headerRow() + ---@param text string? + ---@return Widget + local makeHeaderCell = function(text) + return TableWidgets.CellHeader{children = text} end - nodes = Array.append(nodes, - config.showLength and makeHeaderCell('Length') or nil, - config.showVod and makeHeaderCell('VOD', '60px') or nil, - config.showMatchPage and makeHeaderCell('') or nil - ) - - local header = mw.html.create('tr') - Array.forEach(nodes, function (node) - header:node(node) - end) + local config = self.config + local isCharTable = self.isCharacterTable - return header + return TableWidgets.TableHeader{children = { + TableWidgets.Row{children = WidgetUtil.collect( + makeHeaderCell('Date'), + config.showTier and makeHeaderCell('Tier') or nil, + config.showType and makeHeaderCell('Type') or nil, + config.displayGameIcons and makeHeaderCell() or nil, + config.showIcon and makeHeaderCell() or nil, + makeHeaderCell('Tournament'), + config.showResult and WidgetUtil.collect( + not isCharTable and makeHeaderCell('vs.') or nil, + makeHeaderCell('Picks'), + config.showBans and makeHeaderCell('Bans') or nil, + isCharTable and { + makeHeaderCell(), + makeHeaderCell('Score'), + makeHeaderCell(), + } or nil, + makeHeaderCell('vs. Picks'), + config.showBans and makeHeaderCell('vs. Bans') or nil + ) or nil, + config.showLength and makeHeaderCell('Length') or nil, + config.showVod and TableWidgets.CellHeader{ + align = 'center', + children = 'VOD' + } or nil, + config.showMatchPage and makeHeaderCell() or nil + )} + }} end ---@param game CharacterGameTableGame ---@param opponentIndex number ---@param key string ----@return Html? +---@return Widget? function CharacterGameTable:_displayCharacters(game, opponentIndex, key) local config = self.config - local makeIcon = function(character) - return CharacterIcon.Icon{character = character, size = config.iconSize, date = game.date} - end - local icons = Array.map(game[key][opponentIndex] or {}, makeIcon) - - return mw.html.create('td') - :addClass(config.showSideClass and self:getSideClass(game.extradata, opponentIndex) or nil) - :node(#icons > 0 and table.concat(icons, config.iconSeparator) or nil) + return TableWidgets.Cell{children = MatchSummaryCharacters{ + bg = config.showSideClass and self:getSideClass(game.extradata, opponentIndex) or nil, + characters = game[key][opponentIndex] or {}, + date = game.date, + }} end ----@param match GameTableMatch +---@param match CharacterGameTableMatch ---@param game CharacterGameTableGame ----@return Html? +---@return Widget[]? function CharacterGameTable:displayGame(match, game) if not self.config.showResult then return @@ -353,35 +410,36 @@ function CharacterGameTable:displayGame(match, game) ---@cast pickedBy -nil local pickedVs = pickedBy == 1 and 2 or 1 local opponentRecords = {match.result.opponent, match.result.vs} - return mw.html.create() - :node(self:_displayDraft(game, opponentRecords[pickedBy], false)) - :node(self:_displayScore(game, pickedBy, pickedVs)) - :node(self:_displayDraft(game, opponentRecords[pickedVs], true)) + return WidgetUtil.collect( + self:_displayDraft(game, opponentRecords[pickedBy], pickedBy, false), + self:_displayScore(game, pickedBy, pickedVs), + self:_displayDraft(game, opponentRecords[pickedVs], pickedVs, true) + ) else - return mw.html.create() - :node(self:_displayOpponent(match.result.vs):css('text-align', 'left')) - :node(self:_displayDraft(game, match.result.opponent)) - :node(self:_displayDraft(game, match.result.vs)) + local indexes = match.result.flipped and {2, 1} or {1, 2} + return WidgetUtil.collect( + self:_displayOpponent(match.result.vs), + self:_displayDraft(game, match.result.opponent, indexes[1]), + self:_displayDraft(game, match.result.vs, indexes[2]) + ) end end ---@param game CharacterGameTableGame ----@param opponentRecord match2opponent +---@param opponentRecord standardOpponent +---@param opponentIndex integer ---@param flipped boolean? ----@return Html? -function CharacterGameTable:_displayDraft(game, opponentRecord, flipped) - local opponentIndex = opponentRecord.id - +---@return Widget[]? +function CharacterGameTable:_displayDraft(game, opponentRecord, opponentIndex, flipped) local isCharTable = self.isCharacterTable local opponent = self:_displayOpponent(opponentRecord, flipped) - return mw.html.create() - :node((flipped and isCharTable) and opponent or nil) - :node(self:_displayCharacters(game, opponentIndex, 'picks')) - :node(self.config.showBans and - self:_displayCharacters(game, opponentIndex, 'bans'):addClass('lor-graycard') or nil - ) - :node((not flipped and isCharTable) and opponent or nil) + return WidgetUtil.collect( + (flipped and isCharTable) and opponent or nil, + self:_displayCharacters(game, opponentIndex, 'picks'), + self.config.showBans and self:_displayCharacters(game, opponentIndex, 'bans') or nil, + (not flipped and isCharTable) and opponent or nil + ) end ---@param game CharacterGameTableGame @@ -394,15 +452,17 @@ function CharacterGameTable:_displayScore(game, pickedBy, pickedVs) local toScore = function(opponentId) local isWinner = winner == opponentId - return mw.html.create(isWinner and 'b' or nil) - :wikitext(scores[opponentId] or (isWinner and 'W' or 'L')) + return HtmlWidgets.Span{ + css = {['font-weight'] = isWinner and 'bold' or nil}, + children = scores[opponentId] or (isWinner and 'W' or 'L') + } end - return mw.html.create('td') - :addClass('match-table-score') - :node(toScore(pickedBy)) - :node(SCORE_CONCAT) - :node(toScore(pickedVs)) + return TableWidgets.Cell{children = { + toScore(pickedBy), + SCORE_CONCAT, + toScore(pickedVs), + }} end ---@param game CharacterGameTableGame @@ -410,33 +470,34 @@ end function CharacterGameTable:_displayLength(game) if not self.config.showLength then return end - return mw.html.create('td') - :node(game.length) + return TableWidgets.Cell{children = game.length} end ----@param match GameTableMatch +---@param match CharacterGameTableMatch ---@param game CharacterGameTableGame ----@return Html? +---@return Widget function CharacterGameTable:gameRow(match, game) - local winner = (self.isCharacterTable and game.pickedBy or - match.result.opponent.id) == tonumber(game.winner) and 1 or 2 - - return mw.html.create('tr') - :addClass(self:_getBackgroundClass(winner)) - :node(self:_displayDate(match)) - :node(self:displayTier(match)) - :node(self:_displayType(match)) - :node(self:_displayGameIconForGame(game)) - :node(self:_displayIcon(match)) - :node(self:_displayTournament(match)) - :node(self:displayGame(match, game)) - :node(self:_displayLength(game)) - :node(self:_displayGameVod(game.vod)) - :node(self:_displayMatchPage(match)) + local indexes = ((self.isCharacterTable and game.pickedBy == game.winner) or match.result.flipped) and {2, 1} or {1, 2} + local winner = game.winner == indexes[1] + + return TableWidgets.Row{ + classes = {self:_getBackgroundClass(winner)}, + children = WidgetUtil.collect( + self:_displayDate(match), + self:displayTier(match), + self:_displayType(match), + self:_displayGameIconForGame(game), + self:_displayIcon(match), + self:_displayTournament(match), + self:displayGame(match, game), + self:_displayGameVod(game.vod), + self:_displayMatchPage(match) + ) + } end ---@param frame Frame ----@return Html +---@return Widget function CharacterGameTable.results(frame) local args = Arguments.getArgs(frame) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index c5114fea4d0..daeab491197 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -585,7 +585,7 @@ function MatchTable:buildDisplay() return TableWidgets.Table{ sortable = true, - columns = self:_buildColumnDefinitions(), + columns = self:buildColumnDefinitions(), title = String.nilIfEmpty(self.config.title), children = WidgetUtil.collect( self:headerRow(), @@ -614,9 +614,9 @@ function MatchTable:_titleRow(title) :done() end ----@private +---@protected ---@return table[] -function MatchTable:_buildColumnDefinitions() +function MatchTable:buildColumnDefinitions() local config = self.config return WidgetUtil.collect( { From 2a2143a65d6b515745537273527095f6d584fd9d Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Thu, 26 Feb 2026 23:00:52 +0900 Subject: [PATCH 25/52] lint --- lua/wikis/commons/GameTable/Character.lua | 1 - lua/wikis/commons/MatchTable.lua | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 2a14f79ff5f..292f1aefd1a 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -9,7 +9,6 @@ local Lua = require('Module:Lua') local Arguments = Lua.import('Module:Arguments') local Array = Lua.import('Module:Array') -local CharacterIcon = Lua.import('Module:CharacterIcon') local Class = Lua.import('Module:Class') local Logic = Lua.import('Module:Logic') local Namespace = Lua.import('Module:Namespace') diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index daeab491197..d3ce8b9904a 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -698,7 +698,7 @@ function MatchTable:headerRow() align = 'center', children = 'vs. Opponent' } - ) or nil, + ) or nil, config.showVod and TableWidgets.CellHeader{ align = 'center', children = 'VOD(s)' From 20bda219746387c9045e988efd802416d0c4aab1 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Fri, 27 Feb 2026 19:09:52 +0900 Subject: [PATCH 26/52] adjust matchpage usage --- lua/wikis/commons/MatchPage/Base.lua | 8 +++----- lua/wikis/commons/MatchTable.lua | 1 + stylesheets/commons/BigMatch.scss | 15 ++++----------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/lua/wikis/commons/MatchPage/Base.lua b/lua/wikis/commons/MatchPage/Base.lua index a298d248890..4cfec59f4ac 100644 --- a/lua/wikis/commons/MatchPage/Base.lua +++ b/lua/wikis/commons/MatchPage/Base.lua @@ -512,14 +512,12 @@ function BaseMatchPage:previousMatches() headToHead and AdditionalSection{ css = {flex = '2 0 100%'}, header = 'Head to Head', - bodyClasses = {'match-table-wrapper'}, children = headToHead, } or nil, Array.map(self.opponents, function (opponent) local matchTable = self:_buildMatchTable(opponent) return AdditionalSection{ header = OpponentDisplay.InlineOpponent{opponent = opponent, teamStyle = 'hybrid'}, - bodyClasses = matchTable and {'match-table-wrapper'} or nil, children = matchTable or self:getTournamentIcon() } end) @@ -537,7 +535,7 @@ end ---@private ---@param props table ----@return Html +---@return Widget function BaseMatchPage:_createMatchTable(props) return MatchTable(Table.mergeInto({ addCategory = false, @@ -554,7 +552,7 @@ end ---@private ---@param opponent standardOpponent ----@return Html? +---@return Widget? function BaseMatchPage:_buildMatchTable(opponent) if not BaseMatchPage._isTeamOpponent(opponent) then return @@ -570,7 +568,7 @@ function BaseMatchPage:_buildMatchTable(opponent) end ---@private ----@return Html? +---@return Widget? function BaseMatchPage:_buildHeadToHeadMatchTable() if not Array.all(self.opponents, BaseMatchPage._isTeamOpponent) then return diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index d3ce8b9904a..56dea73cc0a 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -584,6 +584,7 @@ function MatchTable:buildDisplay() end) return TableWidgets.Table{ + classes = {'match-table-wrapper'}, sortable = true, columns = self:buildColumnDefinitions(), title = String.nilIfEmpty(self.config.title), diff --git a/stylesheets/commons/BigMatch.scss b/stylesheets/commons/BigMatch.scss index c4d36717bfb..1d1665b090d 100644 --- a/stylesheets/commons/BigMatch.scss +++ b/stylesheets/commons/BigMatch.scss @@ -1105,19 +1105,12 @@ span.slash { border-color: var( --clr-on-surface-dark-primary-8 ); } - &.match-table-wrapper { - display: block; + &:has( > .match-table-wrapper ) { + display: contents; padding: unset; - @media ( max-width: 767px ) { - overflow-x: auto; - } - - & > table.wikitable { - border-radius: 0.5rem; - overflow: hidden; - height: 100%; - width: 100%; + > .match-table-wrapper { + width: unset; } } } From 873577ecfc889990a3d75ecf38280af1f441b5a7 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:47:26 +0900 Subject: [PATCH 27/52] missing return --- lua/wikis/commons/GameTable.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/commons/GameTable.lua b/lua/wikis/commons/GameTable.lua index 2e6fc5bf3f2..d67d471bef3 100644 --- a/lua/wikis/commons/GameTable.lua +++ b/lua/wikis/commons/GameTable.lua @@ -158,7 +158,7 @@ function GameTable:buildRows() end Array.extendWith(rows, Array.reverse( Array.map(match.games, function (game) - self:gameRow(match, game) + return self:gameRow(match, game) end) )) end) From b193bba3e03e75ae4c543b805efd4574156d6c02 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:52:26 +0900 Subject: [PATCH 28/52] indentation --- lua/wikis/commons/GameTable/Character.lua | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 292f1aefd1a..a92e32ab312 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -361,18 +361,18 @@ function CharacterGameTable:headerRow() config.showIcon and makeHeaderCell() or nil, makeHeaderCell('Tournament'), config.showResult and WidgetUtil.collect( - not isCharTable and makeHeaderCell('vs.') or nil, - makeHeaderCell('Picks'), - config.showBans and makeHeaderCell('Bans') or nil, - isCharTable and { - makeHeaderCell(), - makeHeaderCell('Score'), - makeHeaderCell(), - } or nil, - makeHeaderCell('vs. Picks'), - config.showBans and makeHeaderCell('vs. Bans') or nil - ) or nil, - config.showLength and makeHeaderCell('Length') or nil, + not isCharTable and makeHeaderCell('vs.') or nil, + makeHeaderCell('Picks'), + config.showBans and makeHeaderCell('Bans') or nil, + isCharTable and { + makeHeaderCell(), + makeHeaderCell('Score'), + makeHeaderCell(), + } or nil, + makeHeaderCell('vs. Picks'), + config.showBans and makeHeaderCell('vs. Bans') or nil + ) or nil, + config.showLength and makeHeaderCell('Length') or nil, config.showVod and TableWidgets.CellHeader{ align = 'center', children = 'VOD' From bb1cfe97988c07a8476a25d30363e23bec6d0b05 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:52:58 +0900 Subject: [PATCH 29/52] add missing length display --- lua/wikis/commons/GameTable/Character.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index a92e32ab312..45e4d08244d 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -489,6 +489,7 @@ function CharacterGameTable:gameRow(match, game) self:_displayIcon(match), self:_displayTournament(match), self:displayGame(match, game), + self:_displayLength(game), self:_displayGameVod(game.vod), self:_displayMatchPage(match) ) From ed85fb6a20cbaa2262f619e22ad93f72c496bc73 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:55:02 +0900 Subject: [PATCH 30/52] restore notplayed filter --- lua/wikis/commons/GameTable/Character.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 45e4d08244d..114a19fe5d1 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -31,6 +31,7 @@ local WidgetUtil = Lua.import('Module:Widget/Util') local DRAW_WINNER = 0 local CHARACTER_MODE = 'character' +local NOT_PLAYED = 'notplayed' local SCORE_CONCAT = ' : ' ---@class CharacterGameTableConfig: MatchTableConfig @@ -232,6 +233,9 @@ end ---@param game MatchGroupUtilGame ---@return boolean function CharacterGameTable:filterGame(game) + if game.status == NOT_PLAYED or Logic.isEmpty(game.winner) then + return false + end ---@cast game CharacterGameTableGame game.picks = self:getCharacters(game, self.config.numPicks, self.getCharacterKey) game.bans = self.config.showBans and From 8b39b05cdfd36d6c1dc5bb05635e03e1dca3853a Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:01:39 +0900 Subject: [PATCH 31/52] use compact date as default --- lua/wikis/commons/GameTable/Character.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 114a19fe5d1..375024316da 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -504,6 +504,7 @@ end ---@return Widget function CharacterGameTable.results(frame) local args = Arguments.getArgs(frame) + args.dateFormat = Logic.emptyOr(args.dateFormat, 'compact') return CharacterGameTable(args):readConfig():query():build() end From 7570c8ea03f44066f4d3e290b632284b6af14d26 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:04:13 +0900 Subject: [PATCH 32/52] make length sortable --- lua/wikis/commons/GameTable/Character.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 375024316da..5e7362182ed 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -332,7 +332,6 @@ function CharacterGameTable:buildColumnDefinitions() ) or nil, config.showLength and { align = 'left', - unsortable = true, } or nil, config.showVod and { align = 'left', From 99e740f1f4014ed60949ded753aaae893175c692 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:15:20 +0900 Subject: [PATCH 33/52] add patch display --- lua/wikis/commons/GameTable/Character.lua | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 5e7362182ed..2b512cf52e5 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -25,6 +25,7 @@ local BooleanOperator = Condition.BooleanOperator local ColumnName = Condition.ColumnName local HtmlWidgets = Lua.import('Module:Widget/Html/All') +local Link = Lua.import('Module:Widget/Basic/Link') local MatchSummaryCharacters = Lua.import('Module:Widget/Match/Summary/Characters') local TableWidgets = Lua.import('Module:Widget/Table2/All') local WidgetUtil = Lua.import('Module:Widget/Util') @@ -39,6 +40,7 @@ local SCORE_CONCAT = ' : ' ---@field showSideClass boolean ---@field showBans boolean ---@field showLength boolean +---@field showPatch boolean ---@field numPicks number ---@field numBans number ---@field iconSize string @@ -109,6 +111,7 @@ function CharacterGameTable:readConfig() showSideClass = Logic.nilOr(Logic.readBoolOrNil(args.showSideClass), true), showBans = Logic.nilOr(Logic.readBoolOrNil(args.showBans), true), showLength = Logic.readBool(args.length), + showPatch = Logic.nilOr(Logic.readBoolOrNil(args.showPatch), true), numPicks = self:getNumberOfPicks(), numBans = self:getNumberOfBans(), iconSize = Logic.nilIfEmpty(self.args.iconSize) or '27px', @@ -333,6 +336,9 @@ function CharacterGameTable:buildColumnDefinitions() config.showLength and { align = 'left', } or nil, + config.showPatch and { + align = 'left', + } or nil, config.showVod and { align = 'left', unsortable = true, @@ -376,6 +382,7 @@ function CharacterGameTable:headerRow() config.showBans and makeHeaderCell('vs. Bans') or nil ) or nil, config.showLength and makeHeaderCell('Length') or nil, + config.showPatch and makeHeaderCell('Patch') or nil, config.showVod and TableWidgets.CellHeader{ align = 'center', children = 'VOD' @@ -475,6 +482,26 @@ function CharacterGameTable:_displayLength(game) return TableWidgets.Cell{children = game.length} end +---@private +---@param game CharacterGameTableGame +---@return Widget? +function CharacterGameTable:_displayPatch(game) + if not self.config.showPatch then return end + + if Logic.isEmpty(game.patch) then + return TableWidgets.Cell{} + end + + return TableWidgets.Cell{children = self:getPatchLink(game)} +end + +---@protected +---@param game CharacterGameTableGame +---@return Widget? +function CharacterGameTable:getPatchLink(game) + return Link{link = 'Patch ' .. game.patch, children = game.patch} +end + ---@param match CharacterGameTableMatch ---@param game CharacterGameTableGame ---@return Widget @@ -493,6 +520,7 @@ function CharacterGameTable:gameRow(match, game) self:_displayTournament(match), self:displayGame(match, game), self:_displayLength(game), + self:_displayPatch(game), self:_displayGameVod(game.vod), self:_displayMatchPage(match) ) From 8d21401debeee7b8128f82a587b1def03e5639f9 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:20:35 +0900 Subject: [PATCH 34/52] adjust background setting --- lua/wikis/commons/GameTable.lua | 4 ++-- lua/wikis/commons/GameTable/Character.lua | 4 ++-- lua/wikis/commons/MatchTable.lua | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lua/wikis/commons/GameTable.lua b/lua/wikis/commons/GameTable.lua index d67d471bef3..ad76250de0c 100644 --- a/lua/wikis/commons/GameTable.lua +++ b/lua/wikis/commons/GameTable.lua @@ -126,10 +126,10 @@ end ---@return Widget function GameTable:gameRow(match, game) local indexes = match.result.flipped and {2, 1} or {1, 2} - local winner = game.winner == indexes[1] + local winner = indexes[game.winner] return TableWidgets.Row{ - classes = {self:_getBackgroundClass(winner)}, + classes = {self:getBackgroundClass(winner)}, children = WidgetUtil.collect( self:_displayDate(match), self:displayTier(match), diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 2b512cf52e5..5b09bed21aa 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -507,10 +507,10 @@ end ---@return Widget function CharacterGameTable:gameRow(match, game) local indexes = ((self.isCharacterTable and game.pickedBy == game.winner) or match.result.flipped) and {2, 1} or {1, 2} - local winner = game.winner == indexes[1] + local winner = indexes[game.winner] return TableWidgets.Row{ - classes = {self:_getBackgroundClass(winner)}, + classes = {self:getBackgroundClass(winner)}, children = WidgetUtil.collect( self:_displayDate(match), self:displayTier(match), diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index 56dea73cc0a..cae6c29d246 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -759,7 +759,7 @@ end ---@return Widget function MatchTable:matchRow(match) return TableWidgets.Row{ - classes = {self:_getBackgroundClass(match.result.winner)}, + classes = {self:getBackgroundClass(match.result.winner)}, children = WidgetUtil.collect( self:_displayDate(match), self:displayTier(match), @@ -953,9 +953,10 @@ function MatchTable:_displayMatchPage(match) } end ----@param winner any +---@protected +---@param winner integer ---@return string? -function MatchTable:_getBackgroundClass(winner) +function MatchTable:getBackgroundClass(winner) return winner == 1 and 'recent-matches-bg-win' or winner == 0 and 'recent-matches-bg-tie' or winner == 2 and 'recent-matches-bg-lose' or From 209cd806258a68da354113ed365c1730d85389bb Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:24:53 +0900 Subject: [PATCH 35/52] fix character table mode --- lua/wikis/commons/GameTable/Character.lua | 33 ++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 5b09bed21aa..fdaba3ecdf2 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -65,12 +65,6 @@ local SCORE_CONCAT = ' : ' local CharacterGameTable = Class.new(GameTable, function (self) self.isCharacterTable = self.args.tableMode == CHARACTER_MODE self.isPickedByRequired = self.isCharacterTable - - if not self.isCharacterTable then - self.resultFromRecord = GameTable.resultFromRecord - self.buildConditions = GameTable.buildConditions - self.statsFromMatches = GameTable.statsFromMatches - end end) ---@return integer @@ -175,8 +169,11 @@ function CharacterGameTable:_buildCharacterConditions() return characterConditions end ----@return string +---@return ConditionTree function CharacterGameTable:buildConditions() + if not self.isCharacterTable then + return GameTable.buildConditions(self) + end local lpdbData = mw.ext.LiquipediaDB.lpdb('match2game', { conditions = self:_buildMatchConditions(), query = 'match2id', @@ -190,7 +187,7 @@ function CharacterGameTable:buildConditions() conditions:add(ConditionNode(ColumnName('match2id'), Comparator.eq, game.match2id)) end) - return conditions:toString() + return conditions end ---@param game CharacterGameTableGame @@ -253,19 +250,25 @@ function CharacterGameTable:filterGame(game) return foundPicks or self.config.showGameWithoutCharacters end ----@param record table +---@param record MatchGroupUtilMatch ---@return MatchTableMatchResult? function CharacterGameTable:resultFromRecord(record) - return { - opponent = record.match2opponents[1], - vs = record.match2opponents[2], - winner = tonumber(record.winner), - countGames = true, - } + if self.isCharacterTable then + return { + opponent = record.opponents[1], + vs = record.opponents[2], + winner = tonumber(record.winner), + countGames = true, + } + end + return GameTable.resultFromRecord(self, record) end ---@return {games: {w: number, d: number, l: number}} function CharacterGameTable:statsFromMatches() + if not self.isCharacterTable then + return GameTable.statsFromMatches(self) + end local totalGames = {w = 0, d = 0, l = 0} Array.forEach(self.matches, function(match) From 5ed82fbe26e3672fd736ae836ed2e4bffdd06607 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:27:04 +0900 Subject: [PATCH 36/52] adjust deadlock custom --- lua/wikis/deadlock/GameTable/Character/Custom.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua/wikis/deadlock/GameTable/Character/Custom.lua b/lua/wikis/deadlock/GameTable/Character/Custom.lua index b976950ee86..ad429e1972b 100644 --- a/lua/wikis/deadlock/GameTable/Character/Custom.lua +++ b/lua/wikis/deadlock/GameTable/Character/Custom.lua @@ -12,6 +12,8 @@ local Class = Lua.import('Module:Class') local GameTableCharacter = Lua.import('Module:GameTable/Character') +---@class DeadlockCharacterGameTable: CharacterGameTable +---@operator call(table): CharacterGameTable local CustomGameTableCharacter = Class.new(GameTableCharacter) ---@return integer @@ -27,7 +29,7 @@ function CustomGameTableCharacter:getCharacterKey(opponentIndex, playerIndex) end ---@param frame Frame ----@return Html +---@return Widget function CustomGameTableCharacter.results(frame) local args = Arguments.getArgs(frame) From d1085f58bcaa975c54c2e13c8977000013b1e90a Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:27:59 +0900 Subject: [PATCH 37/52] adjust dota2 custom --- lua/wikis/dota2/GameTable/Character/Custom.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lua/wikis/dota2/GameTable/Character/Custom.lua b/lua/wikis/dota2/GameTable/Character/Custom.lua index c9d71ef36a0..3a2ff227373 100644 --- a/lua/wikis/dota2/GameTable/Character/Custom.lua +++ b/lua/wikis/dota2/GameTable/Character/Custom.lua @@ -12,7 +12,10 @@ local Class = Lua.import('Module:Class') local GameTableCharacter = Lua.import('Module:GameTable/Character') +local Link = Lua.import('Module:Widget/Basic/Link') + ---@class Dota2CharacterGameTable: CharacterGameTable +---@operator call(table): Dota2CharacterGameTable local CustomCharacterGameTable = Class.new(GameTableCharacter) ---@return integer @@ -27,8 +30,15 @@ function CustomCharacterGameTable:getCharacterKey(opponentIndex, playerIndex) return 'team' .. opponentIndex .. 'hero' .. playerIndex end +---@protected +---@param game CharacterGameTableGame +---@return Widget? +function CustomCharacterGameTable:getPatchLink(game) + return Link{link = 'Version ' .. game.patch, children = game.patch} +end + ---@param frame Frame ----@return Html +---@return Widget function CustomCharacterGameTable.results(frame) local args = Arguments.getArgs(frame) From e55451af5888c8532628fd43f82942bf809dcf60 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:30:07 +0900 Subject: [PATCH 38/52] add character table custom to commons --- lua/wikis/commons/GameTable/Character/Custom.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 lua/wikis/commons/GameTable/Character/Custom.lua diff --git a/lua/wikis/commons/GameTable/Character/Custom.lua b/lua/wikis/commons/GameTable/Character/Custom.lua new file mode 100644 index 00000000000..d10d0bb72c4 --- /dev/null +++ b/lua/wikis/commons/GameTable/Character/Custom.lua @@ -0,0 +1,12 @@ +--- +-- @Liquipedia +-- page=Module:GameTable/Character/Custom +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Lua = require('Module:Lua') + +local CharacterGameTable = Lua.import('Module:GameTable/Character') + +return CharacterGameTable From c4048b86aaf84bbb8a7ee71ee8765cb2a46823d7 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:30:28 +0900 Subject: [PATCH 39/52] type annotation --- lua/wikis/commons/GameTable/Custom.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/commons/GameTable/Custom.lua b/lua/wikis/commons/GameTable/Custom.lua index 4a27cd360b0..6a02e0b214e 100644 --- a/lua/wikis/commons/GameTable/Custom.lua +++ b/lua/wikis/commons/GameTable/Custom.lua @@ -14,7 +14,7 @@ local GameTable = Lua.import('Module:GameTable') local CustomGameTable = {} ---@param args table ----@return Html +---@return Widget function CustomGameTable.results(args) return GameTable(args):readConfig():query():build() end From 1024c6d1f56090c397bafe77b2f64110aada68b1 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:33:26 +0900 Subject: [PATCH 40/52] update val custom type annotations --- lua/wikis/valorant/GameTable/Character/Custom.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lua/wikis/valorant/GameTable/Character/Custom.lua b/lua/wikis/valorant/GameTable/Character/Custom.lua index 1f876f3521a..09ee8bdaa99 100644 --- a/lua/wikis/valorant/GameTable/Character/Custom.lua +++ b/lua/wikis/valorant/GameTable/Character/Custom.lua @@ -19,6 +19,7 @@ local Opponent = Lua.import('Module:Opponent/Custom') local CharacterGameTable = Lua.import('Module:GameTable/Character') ---@class ValorantCharacterGameTable: CharacterGameTable +---@operator call(table): ValorantCharacterGameTable local CustomCharacterGameTable = Class.new(CharacterGameTable, function (self) self.args.showBans = false @@ -115,7 +116,7 @@ function CustomCharacterGameTable:_getRatio(participant) return MathUtil.round(kills / deaths, 1) end ----@param match GameTableMatch +---@param match CharacterGameTableMatch ---@param game CharacterGameTableGame ---@return Html? function CustomCharacterGameTable:displayGame(match, game) @@ -166,7 +167,7 @@ function CustomCharacterGameTable:displayGame(match, game) end ---@param frame Frame ----@return Html +---@return Widget function CustomCharacterGameTable.results(frame) local args = Arguments.getArgs(frame) From ef04c283de28c53b5efa41875fceef4eaa978283 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:35:02 +0900 Subject: [PATCH 41/52] organize imports --- lua/wikis/valorant/GameTable/Character/Custom.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lua/wikis/valorant/GameTable/Character/Custom.lua b/lua/wikis/valorant/GameTable/Character/Custom.lua index 09ee8bdaa99..200a97b721e 100644 --- a/lua/wikis/valorant/GameTable/Character/Custom.lua +++ b/lua/wikis/valorant/GameTable/Character/Custom.lua @@ -12,9 +12,8 @@ local Array = Lua.import('Module:Array') local CharacterIcon = Lua.import('Module:CharacterIcon') local Class = Lua.import('Module:Class') local MathUtil = Lua.import('Module:MathUtil') -local Page = Lua.import('Module:Page') - local Opponent = Lua.import('Module:Opponent/Custom') +local Page = Lua.import('Module:Page') local CharacterGameTable = Lua.import('Module:GameTable/Character') From fa3410c5bdd5ef2b8cd22ff983ff4e1c62577879 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 16:53:49 +0900 Subject: [PATCH 42/52] type anno --- lua/wikis/commons/GameTable/Character.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index fdaba3ecdf2..7d1e1aae22a 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -398,7 +398,7 @@ end ---@param game CharacterGameTableGame ---@param opponentIndex number ---@param key string ----@return Widget? +---@return Widget function CharacterGameTable:_displayCharacters(game, opponentIndex, key) local config = self.config @@ -457,7 +457,7 @@ end ---@param game CharacterGameTableGame ---@param pickedBy number ---@param pickedVs number ----@return Html +---@return Widget function CharacterGameTable:_displayScore(game, pickedBy, pickedVs) local winner = tonumber(game.winner) local scores = Array.map(game.opponents, Operator.property('score')) @@ -478,7 +478,7 @@ function CharacterGameTable:_displayScore(game, pickedBy, pickedVs) end ---@param game CharacterGameTableGame ----@return Html? +---@return Widget? function CharacterGameTable:_displayLength(game) if not self.config.showLength then return end From b53ceb40aa0e41276b3f41819c63ca2f81180afa Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:05:43 +0900 Subject: [PATCH 43/52] update val custom impl --- .../valorant/GameTable/Character/Custom.lua | 201 ++++++++++++------ 1 file changed, 140 insertions(+), 61 deletions(-) diff --git a/lua/wikis/valorant/GameTable/Character/Custom.lua b/lua/wikis/valorant/GameTable/Character/Custom.lua index 200a97b721e..b009adf6fcb 100644 --- a/lua/wikis/valorant/GameTable/Character/Custom.lua +++ b/lua/wikis/valorant/GameTable/Character/Custom.lua @@ -17,6 +17,11 @@ local Page = Lua.import('Module:Page') local CharacterGameTable = Lua.import('Module:GameTable/Character') +local LinkWidget = Lua.import('Module:Widget/Basic/Link') +local MatchSummaryCharacters = Lua.import('Module:Widget/Match/Summary/Characters') +local TableWidgets = Lua.import('Module:Widget/Table2/All') +local WidgetUtil = Lua.import('Module:Widget/Util') + ---@class ValorantCharacterGameTable: CharacterGameTable ---@operator call(table): ValorantCharacterGameTable local CustomCharacterGameTable = Class.new(CharacterGameTable, function (self) @@ -65,42 +70,118 @@ function CustomCharacterGameTable:getCharacterKey(opponentIndex, playerIndex) return 't' .. opponentIndex .. 'p' .. playerIndex .. 'agent' end +---@protected +---@return table[] +function CustomCharacterGameTable:buildColumnDefinitions() + local config = self.config + return WidgetUtil.collect( + { + -- Date column + align = 'left', + sortType = 'number', + }, + config.showTier and {align = 'left'} or nil, + config.showType and {align = 'center'} or nil, + config.showIcon and { + align = 'center', + unsortable = true, + } or nil, + { + -- Tournament column + align = 'left', + }, + { + -- Map column + align = 'left', + }, + config.showResult and WidgetUtil.collect( + config.mode == Opponent.solo and {align = 'center'} or nil, + config.mode ~= Opponent.team and { + {align = 'right'}, -- Kills + {align = 'right'}, -- Deaths + {align = 'right'}, -- Assists + {align = 'right'} -- Ratio + } or nil, + { + -- Picks column + align = 'center', + unsortable = true, + }, + { + -- Team column + align = 'center', + }, + { + -- Score column + align = 'center', + }, + { + -- vs. Team column + align = 'center', + }, + { + -- vs. Picks column + align = 'center', + } + ) or nil, + config.showLength and { + align = 'left', + } or nil, + config.showPatch and { + align = 'left', + } or nil, + config.showVod and { + align = 'left', + unsortable = true, + } or nil, + config.showMatchPage and { + align = 'center', + unsortable = true, + } or nil + ) +end + ---@return Html function CustomCharacterGameTable:headerRow() - local makeHeaderCell = function(text, width) - return mw.html.create('th'):css('max-width', width):node(text) + ---@param text string? + ---@return Widget + local makeHeaderCell = function(text) + return TableWidgets.CellHeader{children = text} end local config = self.config - local nodes = Array.append({}, - makeHeaderCell('Date', '100px'), - config.showTier and makeHeaderCell('Tier', '70px') or nil, - config.showType and makeHeaderCell('Type', '70px') or nil, - config.showIcon and makeHeaderCell(nil, '25px'):addClass('unsortable') or nil, - makeHeaderCell('Tournament'), - makeHeaderCell('Map'), - (config.showResult and config.mode == Opponent.solo) and makeHeaderCell('') or nil, - (config.showResult and config.mode ~= Opponent.team) and makeHeaderCell('K') or nil, - (config.showResult and config.mode ~= Opponent.team) and makeHeaderCell('D') or nil, - (config.showResult and config.mode ~= Opponent.team) and makeHeaderCell('A') or nil, - (config.showResult and config.mode ~= Opponent.team) and makeHeaderCell('Ratio') or nil, - config.showResult and makeHeaderCell('Picks'):addClass('unsortable') or nil, - config.showResult and makeHeaderCell(nil, '80px') or nil, - config.showResult and makeHeaderCell('Score') or nil, - config.showResult and makeHeaderCell(nil, '80px') or nil, - config.showResult and makeHeaderCell('vs. Picks'):addClass('unsortable') or nil, - config.showLength and makeHeaderCell('Length') or nil, - config.showVod and makeHeaderCell('VOD', '60px') or nil, - config.showMatchPage and makeHeaderCell('') or nil - ) - - local header = mw.html.create('tr') - Array.forEach(nodes, function (node) - header:node(node) - end) - - return header + return TableWidgets.TableHeader{children = { + TableWidgets.Row{children = WidgetUtil.collect( + makeHeaderCell('Date'), + config.showTier and makeHeaderCell('Tier') or nil, + config.showType and makeHeaderCell('Type') or nil, + config.showIcon and makeHeaderCell() or nil, + makeHeaderCell('Tournament'), + makeHeaderCell('Map'), + config.showResult and WidgetUtil.collect( + config.mode == Opponent.solo and makeHeaderCell() or nil, + config.mode ~= Opponent.team and { + makeHeaderCell('K'), + makeHeaderCell('D'), + makeHeaderCell('A'), + makeHeaderCell('Ratio'), + } or nil, + makeHeaderCell('Picks'), + makeHeaderCell(), + makeHeaderCell('Score'), + makeHeaderCell(), + makeHeaderCell('vs. Picks') + ) or nil, + config.showLength and makeHeaderCell('Length') or nil, + config.showPatch and makeHeaderCell('Patch') or nil, + config.showVod and TableWidgets.CellHeader{ + align = 'center', + children = 'VOD' + } or nil, + config.showMatchPage and makeHeaderCell() or nil + )} + }} end ---@param participant table @@ -117,30 +198,26 @@ end ---@param match CharacterGameTableMatch ---@param game CharacterGameTableGame ----@return Html? +---@return Widget[]? function CustomCharacterGameTable:displayGame(match, game) - local makeCell = function (text) - return mw.html.create('td'):node(text) + ---@param children Renderable|Renderable[]? + ---@return Table2Cell + local makeCell = function (children) + return TableWidgets.Cell{children = children} end - local makeIcon = function (character) - if not character then return nil end - return mw.html.create('td') - :node(CharacterIcon.Icon{character = character, size = self.config.iconSize, date = game.date}) - end + local indexes = ((self.isCharacterTable and game.pickedBy == game.winner) or match.result.flipped) and {2, 1} or {1, 2} - local opponent = match.result.opponent - local opponentVs = match.result.vs - if self.isCharacterTable then - local pickedBy = game.pickedBy - ---@cast pickedBy -nil - if pickedBy == 2 then - opponent, opponentVs = opponentVs, opponent - end - end + local opponent = match.opponents[indexes[1]] + local opponentVs = match.opponents[indexes[2]] - local node = mw.html.create() - :node(makeCell(Page.makeInternalLink(game.map))) + ---@type Widget[] + local cells = {makeCell(LinkWidget{link = game.map})} + + ---@param cell Widget + local function addCell(cell) + table.insert(cells, cell) + end if self.config.mode ~= Opponent.team then local participant = game.opponents[game.pickedBy].players[game.pickedByplayer] @@ -148,21 +225,23 @@ function CustomCharacterGameTable:displayGame(match, game) local index = Array.indexOf(game.picks[game.pickedBy], function (pick) return participant.agent == pick end) - node:node(index > 0 and makeIcon(table.remove(game.picks[game.pickedBy], index)) or makeCell()) + addCell(makeCell(index > 0 and MatchSummaryCharacters{ + characters = {table.remove(game.picks[game.pickedBy], index)} + } or nil)) end - node - :node(makeCell(participant and participant.kills or nil)) - :node(makeCell(participant and participant.deaths or nil)) - :node(makeCell(participant and participant.assists or nil)) - :node(makeCell(participant and self:_getRatio(participant) or nil)) + addCell(makeCell(participant and participant.kills or nil)) + addCell(makeCell(participant and participant.deaths or nil)) + addCell(makeCell(participant and participant.assists or nil)) + addCell(makeCell(participant and self:_getRatio(participant) or nil)) end - return node - :node(self:_displayCharacters(game, opponent.id, 'picks')) - :node(self:_displayOpponent(opponent, false)) - :node(self:_displayScore(game, opponent.id, opponentVs.id)) - :node(self:_displayOpponent(opponentVs, true)) - :node(self:_displayCharacters(game, opponentVs.id, 'picks')) + addCell(self:_displayCharacters(game, indexes[1], 'picks')) + addCell(self:_displayOpponent(opponent, false)) + addCell(self:_displayScore(game, indexes[1], indexes[2])) + addCell(self:_displayOpponent(opponentVs, true)) + addCell(self:_displayCharacters(game, indexes[2], 'picks')) + + return cells end ---@param frame Frame From fc7fda042e0df03b2058523e08e3bf6a865afa9e Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:09:24 +0900 Subject: [PATCH 44/52] adjust column config --- lua/wikis/valorant/GameTable/Character/Custom.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/wikis/valorant/GameTable/Character/Custom.lua b/lua/wikis/valorant/GameTable/Character/Custom.lua index b009adf6fcb..903997ee34c 100644 --- a/lua/wikis/valorant/GameTable/Character/Custom.lua +++ b/lua/wikis/valorant/GameTable/Character/Custom.lua @@ -95,7 +95,7 @@ function CustomCharacterGameTable:buildColumnDefinitions() align = 'left', }, config.showResult and WidgetUtil.collect( - config.mode == Opponent.solo and {align = 'center'} or nil, + config.mode == Opponent.solo and {align = 'left'} or nil, config.mode ~= Opponent.team and { {align = 'right'}, -- Kills {align = 'right'}, -- Deaths @@ -160,14 +160,14 @@ function CustomCharacterGameTable:headerRow() makeHeaderCell('Tournament'), makeHeaderCell('Map'), config.showResult and WidgetUtil.collect( - config.mode == Opponent.solo and makeHeaderCell() or nil, + config.mode == Opponent.solo and makeHeaderCell('Pick') or nil, config.mode ~= Opponent.team and { makeHeaderCell('K'), makeHeaderCell('D'), makeHeaderCell('A'), makeHeaderCell('Ratio'), } or nil, - makeHeaderCell('Picks'), + makeHeaderCell(config.mode == Opponent.solo and 'Team Picks' or 'Picks'), makeHeaderCell(), makeHeaderCell('Score'), makeHeaderCell(), From 3e2274ecfecf25b5f251c4295ae486ed1c1dc425 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:09:49 +0900 Subject: [PATCH 45/52] move date default to constructor --- lua/wikis/commons/GameTable/Character.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 7d1e1aae22a..e1eb94e1f54 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -63,6 +63,7 @@ local SCORE_CONCAT = ' : ' ---@field config CharacterGameTableConfig ---@field matches CharacterGameTableMatch[] local CharacterGameTable = Class.new(GameTable, function (self) + self.args.dateFormat = Logic.emptyOr(self.args.dateFormat, 'compact') self.isCharacterTable = self.args.tableMode == CHARACTER_MODE self.isPickedByRequired = self.isCharacterTable end) @@ -534,7 +535,6 @@ end ---@return Widget function CharacterGameTable.results(frame) local args = Arguments.getArgs(frame) - args.dateFormat = Logic.emptyOr(args.dateFormat, 'compact') return CharacterGameTable(args):readConfig():query():build() end From 207d8d18eca7b368eed5730f839389bc5c4d713d Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:14:17 +0900 Subject: [PATCH 46/52] formatRounded --- lua/wikis/valorant/GameTable/Character/Custom.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/valorant/GameTable/Character/Custom.lua b/lua/wikis/valorant/GameTable/Character/Custom.lua index 903997ee34c..64db3cd4194 100644 --- a/lua/wikis/valorant/GameTable/Character/Custom.lua +++ b/lua/wikis/valorant/GameTable/Character/Custom.lua @@ -185,7 +185,7 @@ function CustomCharacterGameTable:headerRow() end ---@param participant table ----@return number? +---@return string? function CustomCharacterGameTable:_getRatio(participant) local kills = tonumber(participant.kills) or 0 local deaths = tonumber(participant.deaths) or 0 @@ -193,7 +193,7 @@ function CustomCharacterGameTable:_getRatio(participant) return nil end - return MathUtil.round(kills / deaths, 1) + return MathUtil.formatRounded{value = kills / deaths, precision = 1} end ---@param match CharacterGameTableMatch From 3b67520d14afdb740e8e8373c2350ae232886ce1 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:16:23 +0900 Subject: [PATCH 47/52] remove unused import --- lua/wikis/valorant/GameTable/Character/Custom.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/lua/wikis/valorant/GameTable/Character/Custom.lua b/lua/wikis/valorant/GameTable/Character/Custom.lua index 64db3cd4194..192fe2dc56b 100644 --- a/lua/wikis/valorant/GameTable/Character/Custom.lua +++ b/lua/wikis/valorant/GameTable/Character/Custom.lua @@ -9,11 +9,9 @@ local Lua = require('Module:Lua') local Arguments = Lua.import('Module:Arguments') local Array = Lua.import('Module:Array') -local CharacterIcon = Lua.import('Module:CharacterIcon') local Class = Lua.import('Module:Class') local MathUtil = Lua.import('Module:MathUtil') local Opponent = Lua.import('Module:Opponent/Custom') -local Page = Lua.import('Module:Page') local CharacterGameTable = Lua.import('Module:GameTable/Character') From e6feef547dc258f6b07d5ff1e8f419b14cfbb27a Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:41:48 +0900 Subject: [PATCH 48/52] add hover support --- lua/wikis/commons/MatchTable.lua | 6 ++-- stylesheets/commons/MatchTable.scss | 50 +++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/lua/wikis/commons/MatchTable.lua b/lua/wikis/commons/MatchTable.lua index cae6c29d246..eb2abf6dec4 100644 --- a/lua/wikis/commons/MatchTable.lua +++ b/lua/wikis/commons/MatchTable.lua @@ -957,9 +957,9 @@ end ---@param winner integer ---@return string? function MatchTable:getBackgroundClass(winner) - return winner == 1 and 'recent-matches-bg-win' or - winner == 0 and 'recent-matches-bg-tie' or - winner == 2 and 'recent-matches-bg-lose' or + return winner == 1 and 'match-table-row__win' or + winner == 0 and 'match-table-row__draw' or + winner == 2 and 'match-table-row__loss' or nil end diff --git a/stylesheets/commons/MatchTable.scss b/stylesheets/commons/MatchTable.scss index 97a238c7a8f..c4464280feb 100644 --- a/stylesheets/commons/MatchTable.scss +++ b/stylesheets/commons/MatchTable.scss @@ -12,3 +12,53 @@ Template(s): MatchTable .recent-matches-bg-lose { background-color: var( --table-red-background-color, #f9f0f2 ) !important; } + +.match-table-wrapper table.table2__table tr.table2__row--body { + &.match-table-row__win { + background-color: var( --clr-semantic-positive-90 ); + + &:hover { + background-color: var( --clr-semantic-positive-80 ); + } + + .theme--dark & { + background-color: var( --clr-semantic-positive-10 ); + + &:hover { + background-color: var( --clr-semantic-positive-20 ); + } + } + } + + &.match-table-row__draw { + background-color: var( --clr-semantic-gold-90 ); + + &:hover { + background-color: var( --clr-semantic-gold-80 ); + } + + .theme--dark & { + background-color: var( --clr-semantic-gold-10 ); + + &:hover { + background-color: var( --clr-semantic-gold-20 ); + } + } + } + + &.match-table-row__loss { + background-color: var( --clr-semantic-negative-90 ); + + &:hover { + background-color: var( --clr-semantic-negative-80 ); + } + + .theme--dark & { + background-color: var( --clr-semantic-negative-10 ); + + &:hover { + background-color: var( --clr-semantic-negative-20 ); + } + } + } +} From ce5acaafbc5551eec23be7e7be24553aa9602a63 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:42:31 +0900 Subject: [PATCH 49/52] short button in character table --- lua/wikis/commons/GameTable/Character.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index e1eb94e1f54..2749f21044a 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -64,6 +64,7 @@ local SCORE_CONCAT = ' : ' ---@field matches CharacterGameTableMatch[] local CharacterGameTable = Class.new(GameTable, function (self) self.args.dateFormat = Logic.emptyOr(self.args.dateFormat, 'compact') + self.args.matchPageButtonText = Logic.emptyOr(self.args.matchPageButtonText, 'short') self.isCharacterTable = self.args.tableMode == CHARACTER_MODE self.isPickedByRequired = self.isCharacterTable end) From 78666a75b2cd1d82f310444c4b64a724a98aa7b8 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 17:48:35 +0900 Subject: [PATCH 50/52] grayscale for bans --- lua/wikis/commons/GameTable/Character.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 2749f21044a..0bdc700e462 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -404,11 +404,14 @@ end function CharacterGameTable:_displayCharacters(game, opponentIndex, key) local config = self.config - return TableWidgets.Cell{children = MatchSummaryCharacters{ - bg = config.showSideClass and self:getSideClass(game.extradata, opponentIndex) or nil, - characters = game[key][opponentIndex] or {}, - date = game.date, - }} + return TableWidgets.Cell{ + classes = key == 'bans' and {'lor-graycard'} or nil, + children = MatchSummaryCharacters{ + bg = config.showSideClass and self:getSideClass(game.extradata, opponentIndex) or nil, + characters = game[key][opponentIndex] or {}, + date = game.date, + } + } end ---@param match CharacterGameTableMatch From 985ff7f7333ac78fffd0ff94019c54c7d0a148e6 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sun, 1 Mar 2026 12:21:57 +0900 Subject: [PATCH 51/52] use mixin --- stylesheets/commons/MatchTable.scss | 48 +++++++---------------------- 1 file changed, 11 insertions(+), 37 deletions(-) diff --git a/stylesheets/commons/MatchTable.scss b/stylesheets/commons/MatchTable.scss index c4464280feb..86b979e783f 100644 --- a/stylesheets/commons/MatchTable.scss +++ b/stylesheets/commons/MatchTable.scss @@ -13,52 +13,26 @@ Template(s): MatchTable background-color: var( --table-red-background-color, #f9f0f2 ) !important; } -.match-table-wrapper table.table2__table tr.table2__row--body { - &.match-table-row__win { - background-color: var( --clr-semantic-positive-90 ); - - &:hover { - background-color: var( --clr-semantic-positive-80 ); - } - - .theme--dark & { - background-color: var( --clr-semantic-positive-10 ); - - &:hover { - background-color: var( --clr-semantic-positive-20 ); - } - } - } - - &.match-table-row__draw { - background-color: var( --clr-semantic-gold-90 ); +@mixin match-table-colors($row-type, $color-type) { + &.match-table-row__#{$row-type} { + background-color: var( --clr-#{$color-type}-90 ); &:hover { - background-color: var( --clr-semantic-gold-80 ); + background-color: var( --clr-#{$color-type}-80 ); } .theme--dark & { - background-color: var( --clr-semantic-gold-10 ); + background-color: var( --clr-#{$color-type}-10 ); &:hover { - background-color: var( --clr-semantic-gold-20 ); + background-color: var( --clr-#{$color-type}-20 ); } } } +} - &.match-table-row__loss { - background-color: var( --clr-semantic-negative-90 ); - - &:hover { - background-color: var( --clr-semantic-negative-80 ); - } - - .theme--dark & { - background-color: var( --clr-semantic-negative-10 ); - - &:hover { - background-color: var( --clr-semantic-negative-20 ); - } - } - } +.match-table-wrapper table.table2__table tr.table2__row--body { + @include match-table-colors( win, semantic-positive ); + @include match-table-colors( draw, semantic-gold ); + @include match-table-colors( loss, semantic-negative ); } From aebae1b488ec1990431ef036fd5e4681a453a0c2 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Sun, 1 Mar 2026 16:17:36 +0900 Subject: [PATCH 52/52] remove unused args --- lua/wikis/commons/GameTable/Character.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lua/wikis/commons/GameTable/Character.lua b/lua/wikis/commons/GameTable/Character.lua index 0bdc700e462..de1b77d5a99 100644 --- a/lua/wikis/commons/GameTable/Character.lua +++ b/lua/wikis/commons/GameTable/Character.lua @@ -43,8 +43,6 @@ local SCORE_CONCAT = ' : ' ---@field showPatch boolean ---@field numPicks number ---@field numBans number ----@field iconSize string ----@field iconSeparator string ---@class CharacterGameTableGame: MatchGroupUtilGame ---@field picks string[][] @@ -110,8 +108,6 @@ function CharacterGameTable:readConfig() showPatch = Logic.nilOr(Logic.readBoolOrNil(args.showPatch), true), numPicks = self:getNumberOfPicks(), numBans = self:getNumberOfBans(), - iconSize = Logic.nilIfEmpty(self.args.iconSize) or '27px', - iconSeparator = Logic.nilIfEmpty(args.iconSeparator) or '' }) return self