Documentation for this module may be created at Module:MatchListAbstract/doc
local util_args = require('Module:ArgsUtil')
local util_cargo = require('Module:CargoUtil')
local util_esports = require('Module:EsportsUtil')
local util_footnote = require('Module:FootnoteUtil')
local util_game = require('Module:GameUtil')
local util_map = require('Module:MapUtil')
local util_html = require('Module:HtmlUtil')
local util_matches = require('Module:MatchesUtil')
local util_table = require('Module:TableUtil')
local util_text = require('Module:TextUtil')
local util_time = require('Module:TimeUtil')
local util_toggle = require('Module:ToggleUtil')
local util_vars = require('Module:VarsUtil')
local m_team = require('Module:Team')
local Champion = require('Module:Champion')
local i18n = require('Module:i18nUtil')
local Phase = require('Module:Phase')
local LCS = require('Module:LuaClassSystem').class
local lang = mw.getLanguage('en')
local TZ = { 'CD', 'You', 'PST', 'CET', 'KST' }
local NORMAL_TZ = { 'PST', 'CET', 'KST' }
local HIDDENCLASS = 'matches-hiddentab'
local TAB_TOGGLES = {
all = {
show_attr = '.ml-allw',
hide_attr = '.ml-btn',
show_class = 'ml-allw',
hide_class = 'ml-btn',
show_id = 'matchlist-show-all',
hiddenclass = HIDDENCLASS,
},
week = {
show_attr = '.ml-w%s',
hide_attr = '.ml-btn%s',
hiddenclass = HIDDENCLASS,
show_class = 'ml-allw ml-w%s',
hide_class = 'ml-btn ml-btn%s',
},
row = 'ml-allw ml-w%s %s',
daterange = 'ml-btn ml-btn%s %s',
}
local TZ_TOGGLES = {
init = 'You',
order = { 'CD', 'You', 'PST', 'CET', 'KST' },
attrs = {
title = {
CD = i18n.print('Countdown_title'),
You = i18n.print('You_title'),
PST = i18n.print('PST_title'),
CET = i18n.print('CET_title'),
KST = i18n.print('KST_title')
},
}
}
local MATCHLIST_PATCH_TOGGLES = {
init = 'patch_number',
order = { 'patch_all', 'patch_number', 'patch_none' },
showall = 'patch_all',
}
local HASFLEX = false
local HAS_GROUPS = false
local FORCE_ALLOW_PREDICTIONS = false
local h = {}
local p = LCS()
-- will not be overridden in subclasses, just needs to be accessible
p.RS_TOGGLES = {
hiddenclass = 'matchlist-rs-hidden',
order = { 'res', 'sch' },
attrs = {
id = {
sch = 'matchlist-show-schedule'
}
}
}
p.THIS = {}
p.COLSPAN = 4
p.WIDTHS = { 110, 25, 25, 110 }
p.MATCH_LIST_HAS_PATCH = false
function p:init(args)
i18n.init('MatchList')
local matchData = self:getMatchData(util_esports.getOverviewPage(args.page), args)
return self:makeOutput(matchData, args)
end
-- match cargo
function p:getMatchData(page, args)
local matchData = self:getAndRunMatchQuery(page, args)
for i, tab in ipairs(matchData) do
tab.index = i
tab.groupList = {}
for j, row in ipairs(tab) do
row.index = j
self:processMatchRow(row, i, j, args, tab)
end
end
matchData.groupList = h.getListOfGroupsFromCargo(page)
self.THIS = util_matches.determineThis(matchData, args.This)
return matchData
end
function p:getAndRunMatchQuery(page, args)
local outergroup = args.outergroup or 'Tab'
local innergroup = args.innergroup or 'Date'
local matchResult = util_cargo.queryAndCast(self:makeMatchQuery(page, args))
return util_cargo.groupResultOrdered(matchResult, outergroup)
end
function p:makeMatchQuery(page, args)
local query = {
tables = { 'MatchSchedule=MS', 'TournamentGroups=TG1', 'TournamentGroups=TG2' },
join = {
'MS.PageAndTeam1=TG1.PageAndTeam',
'MS.PageAndTeam2=TG2.PageAndTeam',
},
fields = self:makeMatchFields(args),
where = h.makeMatchWhere(page, args),
orderBy = 'MS.N_Page ASC, MS.N_TabInPage ASC, MS.N_MatchInTab ASC',
groupBy = 'MS.MatchId',
}
return query
end
function p:makeMatchFields(args)
local tbl = {
'MS.Team1',
'MS.Team2',
'MS.Team1Final',
'MS.Team2Final',
'MS.Player1',
'MS.Player2',
'MS.Winner [number]',
'MS.Team1Score [number]',
'MS.Team2Score [number]',
'MS.Patch',
'MS.DisabledChampions=Disabled',
'MS.Hotfix',
'MS.PatchFootnote',
'MS.FF [number]',
'MS.Tab',
'MS.N_TabInPage [number]',
'MS.MatchId',
'MS.DateTime_UTC=UTC',
'MS.DST',
'MS.HasTime [boolean]',
'MS.OverviewPage',
'MS.MatchDay',
'MS.Stream',
'MS.IsFlexibleStart [boolean]',
'MS.BestOf [number]',
'MS.InitialN_MatchInTab',
'MS.InitialPageAndTab',
'MS.Phase',
'TG1.GroupName=GroupName1',
'TG2.GroupName=GroupName2',
'CONCAT(N_Page,"_",N_TabInPage)=PageAndTab',
'MS.OverrideAllowPredictions [boolean]',
'MS.OverrideDisallowPredictions [boolean]',
'MS.MatchId',
'MS.PatchPage',
'MS.IsNullified [boolean]',
}
if not util_args.castAsBool(args.nofootnotes) then
util_table.mergeArrays(tbl, { 'MS.Team1Footnote', 'MS.Team2Footnote', 'MS.Footnote' })
end
return tbl
end
function h.makeMatchWhere(page, args)
local tbl = {
('MS.OverviewPage="%s"'):format(page),
util_cargo.whereFromArg('MS.Tab="%s"', args.onlytab),
}
if args.team then
local teamlink = m_team.teamlinkname(args.team)
tbl[#tbl+1] = ('(MS.Team1Final="%s" OR MS.Team2Final="%s")'):format(teamlink, teamlink)
end
return util_cargo.concatWhere(tbl)
end
function p:processMatchRow(row, i, j, args, tab)
h.processDateTime(row, i, j, h.lastRow(tab, j))
util_esports.setScoreDisplays(row)
row.innergroup = args.innergroup and row[args.innergroup]
row.NewDay = h.nextRow(tab, j).MatchDay and h.nextRow(tab, j).MatchDay ~= row.MatchDay
row.TimestampAttr = h.getTimestampAttr(row, tab, j)
row.GroupName = h.getRowGroupName(row)
HAS_GROUPS = HAS_GROUPS or row.Phase
row.Phase = Phase(row.Phase)
HASFLEX = HASFLEX or row.IsFlexibleStart
FORCE_ALLOW_PREDICTIONS = FORCE_ALLOW_PREDICTIONS or row.OverrideAllowPredictions
self:getAndAddPatchData(row, tab)
end
function h.lastRow(tab, j)
return tab[j-1] or { NewDay = true }
end
function h.nextRow(tab, j)
return tab[j+1] or {}
end
function h.processDateTime(row, i, j, lastrow)
if row.UTC then
h.processDateTimeKnown(row, i, j, lastrow)
else
h.setDatesTBD(row)
end
end
function h.processDateTimeKnown(row, i, j, lastrow)
util_time.addTimezonesToRowFromUTC(row)
for _, tz in ipairs(NORMAL_TZ) do
row[tz .. '_Time'] = row[tz]:match('(%d%d:%d%d)')
local date = lang:formatDate('D Y-m-d',row[tz])
row[tz .. '_Date'] = date
row[tz .. '_isNewDate'] = date ~= lastrow[tz .. '_Date']
end
if row.HasTime then
h.setTimesYou(row, row.UTC, i, j)
else
h.setTimesTBD(row)
end
end
function h.setTimesYou(row, UTC, i, j)
row.You_Time = util_time.timeInLocal(UTC)
row.CD_Time = util_time.countdown(UTC, h.getCountdownSettings(row, i, j))
row.You_Date = util_time.dateInLocal(UTC)
row.You_Date_Range = util_time.dateInLocalMatches(UTC)
end
function h.getCountdownSettings(row, i, j)
if row.Stream then
return {
options = { 'matches-format', 'no-leading-zeros' },
data_end = 'toggle',
default = '<span class="matchlist-streamlink-icon"></span>',
i = ('%s_%s'):format(i, j)
}
else
return {
options = { 'matches-format', 'no-leading-zeros' }
}
end
end
function h.setTimesTBD(row)
for _, tz in ipairs(TZ) do
row[tz .. '_Time'] = 'TBD'
end
row.You_Date = 'TBD'
row.You_Date_Range = 'TBD'
return
end
function h.setDatesTBD(row)
for _, tz in ipairs(TZ) do
row[tz .. '_Date'] = 'TBD'
end
row.You_Date = 'TBD'
row.You_Date_Range = 'TBD'
h.setTimesTBD(row)
end
function h.getTimestampAttr(row, tab, j)
if h.lastRow(tab, j).NewDay then
return util_time.unixNumber(row.UTC)
else
return h.lastRow(tab, j).TimestampAttr
end
end
function h.getRowGroupName(row)
if row.Team1 == 'TBD' then return row.GroupName2 end
if row.Team2 == 'TBD' then return row.GroupName1 end
if row.GroupName1 ~= row.GroupName2 then return nil end
return row.GroupName1
end
function h.getListOfGroupsFromCargo(page)
return util_cargo.getOrderedList({
tables = 'TournamentGroups',
fields = 'GroupName',
where = ('OverviewPage="%s"'):format(page),
orderBy = 'GroupN',
groupBy = 'GroupName',
}, 'GroupName')
end
function p:getAndAddPatchData(row, tab)
-- add patch as a table caption
util_table.initTable(tab, 'patchinfo', { patches = {}, footnotes = {} })
if not row.Patch then return true end
self.MATCH_LIST_HAS_PATCH = true
local patch = row.Patch
util_table.initDict(
tab.patchinfo.patches,
patch,
{ patchLink = util_text.intLinkOrText(row.PatchPage, patch), hotfixes = {}, disabled = {}, linked = {} }
)
if row.Hotfix then
util_table.initDict(tab.patchinfo.patches[patch].hotfixes, row.Hotfix)
end
if row.PatchFootnote then
util_table.initDict(tab.patchinfo.footnotes, row.PatchFootnote)
end
util_table.mergeArrays(tab.patchinfo.patches[patch].disabled, util_text.split(row.Disabled))
end
-- print
function p:makeOutput(data, args)
util_footnote.init()
local output = mw.html.create('div'):attr('id','matchlist')
if HASFLEX then
h.printFlexNote(output)
end
self:printTogglersAndButtons(output, data.groupList)
self:printAllTabs(output, data, args)
util_footnote.printTexts(output)
return output
end
function p:printTogglersAndButtons(tbl, groupList)
util_toggle.printSectionToggler(tbl, TAB_TOGGLES.all)
h.printResultsToggle(tbl)
h.printTZToggle(tbl)
self:printPredictionsButton(tbl)
h.printGroups(tbl, groupList)
tbl:tag('div')
:css('clear','left')
:attr('id', 'matchlist-section-start')
end
function h.printResultsToggle(tbl)
local div = tbl:tag('div')
:addClass('toggle-button')
p.RS_TOGGLES.displays = {
res = i18n.print('results_init'),
sch = i18n.print('results')
}
util_toggle.printOptionFromListTogglers(div, p.RS_TOGGLES)
end
function h.printTZToggle(tbl)
local div = tbl:tag('div'):addClass('toggle-button')
util_toggle.printOptionFromListTogglers(div, TZ_TOGGLES)
end
function p:printPredictionsButton(tbl)
if self.THIS and #self.THIS == 0 and not FORCE_ALLOW_PREDICTIONS then return end
local div = tbl:tag('div')
:addClass('toggle-button')
:addClass('prediction-action')
:attr('id', 'matches-prediction-begin')
:wikitext(i18n.print('launch_predictions'))
end
function h.printGroups(tbl, groupList)
if not next(groupList) then return end
if not HAS_GROUPS then return end
tbl:tag('div')
:css('clear','left')
local div = tbl:tag('div')
:addClass('toggle-button')
div:tag('span'):wikitext(i18n.print('highlightGroup'))
util_map.arraySafe(groupList, h.printOneGroup, div)
end
function h.printOneGroup(group, div)
div:wikitext('|')
div:tag('div')
:addClass('group-highlighter')
:wikitext(group)
:attr('data-group-highlighter', group)
end
function h.printFlexNote(tbl)
tbl:wikitext(mw.getCurrentFrame():expandTemplate{title='FlexNotice',args={''}})
end
function p:printAllTabs(output, data, args)
local div = output:tag('div')
:attr('id', 'matchlist-content-wrapper')
:addClass('ml-normal-pred-and-results')
:addClass(args.tabwrapperclass)
for i, tab in ipairs(data) do
self:printTab(div, tab, util_table.keyOf(self.THIS, i))
end
end
function p:printTab(div, tab, isfocused)
local innerDiv = div:tag('div')
:addClass('matchlist-tab-wrapper')
local tbl = innerDiv:tag('table')
:addClass('wikitable2')
:addClass('matchlist')
self:printPatchCaption(tbl, tab)
self:printTabHeader(tbl, tab.index, tab.name, isfocused)
self:printDateRange(
tbl,
tab,
h.getDateRangeToggleClass(tab.index, isfocused)
)
util_html.printEmptyWidthRowPX(tbl, self.WIDTHS)
local toggle_class = h.getRowToggleClass(tab.index, isfocused)
for k, row in ipairs(tab) do
self:printFullRow(tbl, row, toggle_class, k == 1)
end
h.printPredictionTotals(innerDiv, tab.predictionTotals)
return
end
function p:printPatchCaption(tbl, tab)
if not self.MATCH_LIST_HAS_PATCH then return true end
local caption = tbl:tag('caption')
-- the toggle here is needed because footnotes are attached only to the parent element, not the spans
-- since it's impossible to show disabled champions but not patch number,
-- we don't need to add patch_disabled since that would be showall, and showall is auto added
h.printPatches(caption, tab.patchinfo)
util_footnote.tag(caption, tab.patchinfo.footnotes)
end
function h.printPatches(caption, patchinfo)
local span = caption:tag('span')
:addClass('matchlist-patches')
local patchesText = {}
for _, patch in ipairs(patchinfo.patches) do
patchesText[#patchesText+1] = h.getOnePatchText(patch, patchinfo["patches"][patch])
end
span:wikitext(i18n.print('patchCaption', h.joinPatchesText(patchesText)))
end
function h.joinPatchesText(patches)
return util_table.concatNonempty(patches, ', ') or i18n.print('tbd')
end
function h.getOnePatchText(patch, patchData)
local patchSpan = mw.html.create("span")
:wikitext(patchData.patchLink)
h.addDisabledText(patchSpan, patchData.disabled)
return tostring(patchSpan)
end
function h.addDisabledText(patchSpan, disabled)
if not next(disabled) then return end
local popup = util_toggle.popupButton(patchSpan)
popup.button:addClass("matchlist-disabled-button")
local div = popup.inner:tag("div")
:wikitext(i18n.print('patchDisabled'))
:addClass("matchlist-disabled-popup")
h.getDisabledChampionFlairs(disabled, div)
end
function h.getDisabledChampionFlairs(disabled, div)
local ul = div:tag("ul")
:addClass("matchlist-disabled-ul")
for _, disabledChampion in ipairs(util_table.removeDuplicates(disabled)) do
ul:tag("li")
:wikitext(Champion(disabledChampion):flairlink())
:addClass("matchlist-disabled-li")
end
end
function p:printTabHeader(tbl, i, name, isfocused)
local data = mw.clone(TAB_TOGGLES.week)
util_toggle.prepDataByWeek(data, i)
data.initshown = isfocused
util_toggle.printToggleHeader(tbl, self.COLSPAN, name, data)
return
end
function p:printDateRange(tbl, tab_data, daterowclass)
local tr = tbl:tag('tr')
:addClass(daterowclass)
local td = tr:tag('td')
:attr('colspan', self.COLSPAN)
local n = #tab_data
for _, tz in ipairs(NORMAL_TZ) do
h.printNormalTZDatesToRangeCell(
td:tag('span'),
tz,
tab_data[1][tz .. '_Date'],
tab_data[n][tz .. '_Date']
)
end
self:printYouTZDatesToRangeCell(
td:tag('span'),
tab_data[1].You_Date_Range,
tab_data[n].You_Date_Range
)
end
function h.printNormalTZDatesToRangeCell(span, tz, text1, text2)
text1 = text1 ~= 'TBD' and lang:formatDate('D j M', text1) or text1
text2 = text2 ~= 'TBD' and lang:formatDate('D j M', text2) or text2
span:addClass('matchlist-daterange') -- for css, toggle displays handled through util below
:wikitext(text1)
if text2 ~= text1 then
span:wikitext(' - ')
span:wikitext(text2)
end
util_toggle.oflCellClasses(span, TZ_TOGGLES, tz)
return
end
function p:printYouTZDatesToRangeCell(span, text1, text2)
-- this is a special case because we need extra classes for JS to do its thing
-- because we don't know if the dates are the same or not
-- given that this is already special, we may as well do You and CD as one instead of splitting
util_toggle.oflCellClasses(span, TZ_TOGGLES, 'You')
span:addClass(TZ_TOGGLES.classes.CD) -- manually add this one because it's the same as YOU
self:printYouTZDatesToRangeData(span, text1, text2)
end
function p:printYouTZDatesToRangeData(span, text1, text2)
span:addClass('matchlist-daterange')
:addClass('matchlist-daterange-you')
span:tag('span')
:addClass('matchlist-daterange-you-1')
:wikitext(text1)
span:tag('span')
:addClass('matchlist-daterange-you-hyphen')
:wikitext(' - ')
span:tag('span')
:addClass('matchlist-daterange-you-hyphen')
:wikitext(text2)
end
function h.getDateRangeToggleClass(i, isfocused)
return TAB_TOGGLES.daterange:format(i, not isfocused and '' or HIDDENCLASS)
end
function h.getRowToggleClass(i, isfocused)
return TAB_TOGGLES.row:format(i, isfocused and '' or HIDDENCLASS)
end
function p:printFullRow(tbl_tab, row, toggle_class, isfirst)
self:printDateRow(tbl_tab, row, toggle_class, isfirst)
local tr = tbl_tab:tag('tr')
:addClass(toggle_class)
:addClass('ml-row')
:attr('data-initial-order', row.InitialN_MatchInTab)
:attr('data-initial-pageandtab', row.InitialPageAndTab)
if row.IsNullified then
tr:addClass('ml-nullified')
end
h.addGroupAttr(tr, row)
h.tagRowIfOver(tr, row)
h.addDateAttr(tr, row)
h.addNewDay(tr, row)
h.addFlexStart(tr, row)
util_matches.printCustomClass(tr, row)
self:printTeam1(tr, row)
self:printMiddle(tr, row)
self:printTeam2(tr, row)
end
function h.addGroupAttr(tr, row)
if not row.Phase:isgroups() then return end
tr:attr('data-group', row.GroupName)
end
function p:printDateRow(tbl, row, toggle_class, isfirst)
for _, tz in ipairs(NORMAL_TZ) do
if row[tz .. '_isNewDate'] then
local tr = tbl:tag('tr')
:addClass(toggle_class)
self:printNormalTZDateToRow(tr, tz, row[tz .. '_Date'])
end
end
local tr = tbl:tag('tr')
:addClass(toggle_class)
self:printYouTZDateToRow(tr, row.You_Date)
tr:attr('data-isfirst',util_args.boolToStringYN(isfirst))
return
end
function p:printNormalTZDateToRow(tr, tz, text)
tr:addClass('matchlist-date')
util_toggle.oflCellClasses(tr, TZ_TOGGLES, tz)
local td = tr:tag('td')
:attr('colspan', self.COLSPAN)
td:wikitext(text)
return
end
function p:printYouTZDateToRow(tr, text)
tr:addClass('matchlist-date')
:addClass('matchlist-you-date') -- to select and remove the 'You' rows
:addClass(TZ_TOGGLES.classes.CD)
util_toggle.oflCellClasses(tr, TZ_TOGGLES, 'You')
local td = tr:tag('td')
:attr('colspan', self.COLSPAN)
td:wikitext(text)
return td
end
function h.tagRowIfOver(tr, row)
if not h.isOver(row) then
tr:addClass('ml-row-tbd')
:attr('data-prediction-expire', row.TimestampAttr)
end
if row.OverrideAllowPredictions then
tr:addClass('ml-row-tbd')
:attr('data-prediction-expire', '9999999999')
tr:addClass('ml-verify-prediction-expire')
tr:attr('data-game-id', row.MatchId)
end
if row.OverrideDisallowPredictions then
tr:addClass('ml-row-predictions-disabled')
tr:attr('data-game-id', row.MatchId)
end
end
function h.isOver(row)
return ((row.Team1Score and row.Team2Score) or row.Winner) and not row.OverrideAllowPredictions
end
function h.addDateAttr(tr, row)
if not row.UTC then return end
tr:attr('data-date',util_time.strToDateStr(row.UTC))
end
function h.addNewDay(tr, row)
if not row.NewDay then return end
tr:addClass('matchlist-newday')
end
function h.addFlexStart(tr, row)
if not util_args.castAsBool(row.IsFlexibleStart) then return end
tr:addClass('matchlist-flex')
end
function p:printTeam1(tr, row)
if (row.Team1 == 'TBD' or not row.Team1) and not row.Player1 then
tr:addClass('ml-row-unknown-team')
end
local td = tr:tag('td')
:addClass('matchlist-team1')
:addClass('ml-team')
util_esports.addTeamHighlighter(td, row.Team1Final)
h.printWinnerClasses(td, row, 1)
h.printTeam1Content(td, row)
end
function h.printWinnerClasses(td, row, this)
if row.Winner == this then
td:addClass('matchlist-winner-team')
elseif row.Winner == 0 then
td:addClass('matchlist-tied-team')
end
end
function h.printTeam1Content(td, row)
util_footnote.tag(td, row.Team1Footnote)
if not row.Player1 then
td:wikitext(m_team.leftshort(row.Team1, {link=''}))
return
end
td
:wikitext(util_esports.playerLinked(row.Player1))
:wikitext(m_team.onlyimageshort(row.Team1))
end
function p:printTeam2(tr, row)
if (row.Team2 == 'TBD' or not row.Team2) and not row.Player2 then
tr:addClass('ml-row-unknown-team')
end
local td = tr:tag('td')
:addClass('matchlist-team2')
:addClass('ml-team')
util_esports.addTeamHighlighter(td, row.Team2Final)
h.printWinnerClasses(td, row, 2)
h.printTeam2Content(td, row)
end
function h.printTeam2Content(td, row)
if not row.Player2 then
td:wikitext(m_team.rightshort(row.Team2, {link=''}))
else
td:wikitext(m_team.onlyimageshort(row.Team2))
:wikitext(util_esports.playerLinked(row.Player2))
end
util_footnote.tag(td, h.team2Footnotes(row))
end
function h.team2Footnotes(row)
local tbl = { row.Team2Footnote, row.Footnote }
util_table.removeFalseEntries(tbl, 2)
return tbl
end
function p:printMiddle(tr, row)
if h.isOver(row) then
self:printScore(tr, row)
self:printTime(tr, row, true)
else
self:printTime(tr, row)
end
end
function p:printScore(tr, row, suppresstoggle)
local td1 = self:printOneScore(tr, row, 1)
local td2 = self:printOneScore(tr, row, 2)
if row.Winner == 1 then
td1:addClass('matchlist-winner-score')
elseif row.Winner == 2 then
td2:addClass('matchlist-winner-score')
elseif row.Winner == 0 then
td1:addClass('matchlist-tied-score')
td2:addClass('matchlist-tied-score')
end
if not suppresstoggle then
util_toggle.oflCellClasses(td1, p.RS_TOGGLES, 'res')
util_toggle.oflCellClasses(td2, p.RS_TOGGLES, 'res')
end
return
end
function p:printOneScore(tr, row, n)
local td = tr:tag('td'):wikitext(self:getOneScore(row, n))
:addClass('matchlist-score')
util_esports.addTeamHighlighter(td, row[('Team%sFinal'):format(n)])
return td
end
function p:getOneScore(row, n)
return row[('Team%sScoreDisplay'):format(n)]
end
function p:printTime(tr, row, usetoggle)
local td = tr:tag('td')
:attr('colspan', self.COLSPAN - 2)
:addClass('matchlist-time-cell')
:addClass('plainlinks')
if usetoggle then
util_toggle.oflCellClasses(td, p.RS_TOGGLES, 'sch')
end
for _, tz in ipairs(TZ) do
local span = td:tag('span')
if row.Stream then
span:wikitext('[', row.Stream, ' ', row[tz .. '_Time'], ']')
else
span:wikitext(row[tz .. '_Time'])
end
util_toggle.oflCellClasses(span, TZ_TOGGLES, tz)
end
return
end
function h.printPredictionTotals(div, totals)
if not totals then return end
local inner = div:tag('div')
:addClass('ml-user-prediction-totals')
inner:wikitext(('Correct: %s/%s (%s Guessed)'):format(totals.right, totals.over, totals.made))
end
return p