local p = {}
-- This module requires the use of the following modules:
local colorContrastModule = require('Module:Color contrast')
local htmlColor = mw.loadData('Module:Color contrast/colors')
local langModule = require("Module:Lang")
local mathModule = require('Module:Math')
local tableEmptyCellModule = require('Module:Table empty cell')
local yesNoModule = require('Module:Yesno')
-- mw.html object for the generated row.
local row
-- Variable that will decide the colspan= of the Short Summary cell.
local nonNilParams = 0
-- Variable that will keep track if a TBA value was entered.
local cellValueTBA = false
-- Variable that handles the assigned tracking categories.
local trackingCategories = ""
-- List of tracking categories.
local trackingCategoryList = {
["air_dates"] = "Category:Episode lists with unformatted air dates",
["alt_air_dates"] = "Category:Episode lists with incorrectly formatted alternate air dates",
["faulty_line_colors"] = "Category:Episode lists with faulty line colors",
["non_compliant_line_colors"] = "Category:Episode lists with non-compliant line colors",
["default_line_colors"] = "Category:Episode list using the default LineColor",
["row_deviations"] = "Category:Episode lists with row deviations",
["invalid_top_colors"] = "Category:Episode lists with invalid top colors",
["tba_values"] = "Category:Episode lists with TBA values",
["nonmatching_numbered_parameters"] = "Category:Episode lists with a non-matching set of numbered parameters",
["raw_unformatted_storyteleplay"] = "Category:Episode lists with unformatted story or teleplay credits",
}
-- List of parameter names in this order.
local cellNameList = {
'EpisodeNumber',
'EpisodeNumber2',
'Title',
'Aux1',
'DirectedBy',
'WrittenBy',
'Aux2',
'Aux3',
'OriginalAirDate',
'AltDate',
'Guests',
'MusicalGuests',
'ProdCode',
'Viewers',
'Aux4'
}
-- List of pairs which cannot be used together
local excludeList = {
['Guests'] = 'Aux1',
['MusicalGuests'] = 'Aux2'
}
-- List of cells that have parameter groups
local parameterGroupCells = {}
local firstParameterGroupCell
local parameterGroupCellsAny = false
-- List of title parameter names in this order.
-- List used for multi title lists.
local titleList = {
'Title',
'RTitle',
'AltTitle',
'RAltTitle',
'NativeTitle',
'TranslitTitle',
}
-- Local function which is used to retrieve the episode number or production code number,
-- without any additional text.
local function idTrim(val, search)
local valFind = string.find(val, search)
if (valFind == nil) then
return val
else
return string.sub(val, 0, valFind-1)
end
end
-- Local function which is used to validate that a parameter has an actual value.
local function hasValue(param)
if (param ~= nil and param ~= "") then
return true
else
return false
end
end
-- Local function which is used to create a table data cell.
local function createTableData(text, rowSpan, textAlign)
if (rowSpan ~= nil and tonumber(rowSpan) > 1) then
row:tag('td')
:attr('rowspan', rowSpan)
:wikitext(text)
else
row:tag('td')
:css('text-align', textAlign)
:wikitext(text)
end
end
-- Local function which is used to add a tracking category to the page.
local function addTrackingCategory(category)
trackingCategories = trackingCategories .. category
end
-- Local function which is used to create a Short Summary row.
local function createShortSummaryRow(args, lineColor)
-- fix for lists in the Short Summary
local shortSummaryText = args.ShortSummary
if (shortSummaryText:match('^[*:;#]') or shortSummaryText:match('^{|')) then
shortSummaryText = '\n' .. shortSummaryText
end
if (shortSummaryText:match('\n[*:;#]')) then
shortSummaryText = shortSummaryText .. '\n'
end
local shortSummaryDiv = mw.html.create('div')
:addClass('shortSummaryText')
:css('max-width', '90vw')
:css('position', 'sticky')
:css('left', '0.2em')
:newline()
:wikitext(shortSummaryText)
local shortSummaryCell = mw.html.create('td')
:addClass('description')
:css('border-bottom', 'solid 3px ' .. lineColor)
:attr('colspan', nonNilParams)
:newline()
:node(shortSummaryDiv)
return mw.html.create('tr')
:addClass('expand-child')
:node(shortSummaryCell)
end
-- Local function which is used to add tracking categories for Top Color issues.
local function addTopColorTrackingCategories(args)
if (hasValue(args.TopColor)) then
addTrackingCategory(trackingCategoryList["row_deviations"])
-- Track top colors that have a color contrast rating below AAA with
-- respect to text color, link color, or visited link color. See
-- WP:COLOR for more about color contrast requirements.
local textContrastRatio = colorContrastModule._ratio{args.TopColor, 'black', ['error'] = 0}
local linkContrastRatio = colorContrastModule._ratio{args.TopColor, '#0B0080', ['error'] = 0}
local visitedLinkContrastRatio = colorContrastModule._ratio{args.TopColor, '#0645AD', ['error'] = 0}
if (textContrastRatio < 7 or linkContrastRatio < 7 or visitedLinkContrastRatio < 7) then
addTrackingCategory(trackingCategoryList["invalid_top_colors"])
end
end
end
-- Local function which is used to add tracking categories for Line Color issues.
local function addLineColorTrackingCategories(args)
if (hasValue(args.LineColor)) then
local blackContrastRatio = colorContrastModule._ratio{args.LineColor, 'black', ['error'] = 0}
local whiteContrastRatio = colorContrastModule._ratio{'white', args.LineColor, ['error'] = 0}
if (colorContrastModule._lum(args.LineColor) == '') then
addTrackingCategory(trackingCategoryList["faulty_line_colors"])
elseif (blackContrastRatio < 7 and whiteContrastRatio < 7) then
addTrackingCategory(trackingCategoryList["non_compliant_line_colors"])
end
else
addTrackingCategory(trackingCategoryList["default_line_colors"])
end
end
-- Local function which is used to set the text of an empty cell
-- with either "TBD" or "N/A".
-- Set to N/A if viewers haven't been available for four weeks, else set it as TBD.
local function setTBDStatus(args, awaitingText, expiredText, weeks)
if args.OriginalAirDate == nil or args.OriginalAirDate == '' then
return tableEmptyCellModule._main({alt_text = awaitingText, title_text = awaitingText})
end
-- If it hasn't aired, change to "N/A".
if string.match(args.OriginalAirDate, '^Unaired') then
return tableEmptyCellModule._main({alt_text = "N/A"})
end
local month, day, year = args.OriginalAirDate:gsub(" ", " "):match("(%a+) (%d+), (%d+)")
if (month == nil) then
day, month, year = args.OriginalAirDate:gsub(" ", " "):match("(%d+) (%a+) (%d+)")
end
if (day == nil) then
return tableEmptyCellModule._main({alt_text = "TBD"})
else
-- List of months.
local monthList = {
['January'] = 1,
['February'] = 2,
['March'] = 3,
['April'] = 4,
['May'] = 5,
['June'] = 6,
['July'] = 7,
['August'] = 8,
['September'] = 9,
['October'] = 10,
['November'] = 11,
['December'] = 12
}
if not monthList[month] then
error('Invalid month ' .. month)
end
local seconds = os.time() - os.time({year = year, month = monthList[month], day = day, hour = 0, min = 0, sec = 0})
if (seconds >= 60 * 60 * 24 * 7 * weeks) then
return tableEmptyCellModule._main({alt_text = expiredText, title_text = expiredText})
else
return tableEmptyCellModule._main({alt_text = awaitingText, title_text = awaitingText})
end
end
end
-- Local function which is used to create an empty cell.
local function createEmptyCell(args, v, unsetParameterGroup)
if (unsetParameterGroup) then
args[v] = tableEmptyCellModule._main({alt_text = "N/A"})
elseif (v == 'Viewers' and hasValue(args.OriginalAirDate)) then
args[v] = setTBDStatus(args, "TBD", "N/A", 4)
elseif (v == 'DirectedBy' or v == 'WrittenBy') then
args[v] = setTBDStatus(args, "TBA", "Unknown", 4)
else
args[v] = tableEmptyCellModule._main({})
end
end
-- Air dates that don't use {{Start date}}
local function checkUsageOfDateTemplates(args, v, onInitialPage, title)
if v == 'OriginalAirDate'
and args[v] ~= ''
and string.match(args[v], '%d%d%d%d') -- Ensure it contains a four-digit number (likely a year)
and string.match(args[v], '2C2C2C') == nil -- Avoids this specific pattern
and not string.match(args[v], '^Unaired') -- Exclude anything starting with "Unaired"
and string.find(args[v], 'itvstart') == nil -- Avoids a {{Start date}} unique class
and onInitialPage
and title.namespace == 0
then
addTrackingCategory(trackingCategoryList["air_dates"])
end
-- Alternate air dates that do use {{Start date}}
if (v == 'AltDate' and args[v] ~= '' and string.find(args[v], 'dtstart') ~= nil and onInitialPage and title.namespace == 0) then
addTrackingCategory(trackingCategoryList["alt_air_dates"])
end
end
-- Local function which is used to create a Production Code cell.
local function createProductionCodeCell(args, v, numberOfParameterGroups)
local thisRowspan
if (not parameterGroupCells[v] and parameterGroupCellsAny) then
thisRowspan = numberOfParameterGroups
else
thisRowspan = 1
end
if (hasValue(args.ProdCode) and string.find(args.ProdCode, 'TBA') == nil) then
row:tag('td')
:attr('id', 'pc' .. idTrim(idTrim(args.ProdCode, ' ----'), '<'))
:attr('rowspan', thisRowspan)
:css('text-align', 'center')
:wikitext(args.ProdCode)
elseif (args.ProdCode == or string.find(args.ProdCode or , 'TBA') ~= nil) then
createEmptyCell(args, v, false)
createTableData(args.ProdCode, thisRowspan, "center")
else
-- ProductionCode parameter not used; Do nothing.
end
nonNilParams = nonNilParams + 1
end
--[[
Local function which is used to extract data
from the numbered serial parameters (Title1, Aux1, etc.), and then convert them to
use the non-numbered parameter names (Title, Aux).
The function returns the args as non-numbered prameter names.
]]--
local function extractDataFromNumberedSerialArgs(args, i, numberOfParameterGroups, title)
for _, v in ipairs(cellNameList) do
local parameter = v
local numberedParameter = v .. "_" .. i
local excludeParameter = excludeList[parameter] or 'NULL' .. parameter
local excludeNumberParameter = (excludeList[numberedParameter] or 'NULL' .. parameter) .. "_" .. i
if (not hasValue(args[numberedParameter]) and not hasValue(args[excludeNumberParameter])
and hasValue(parameterGroupCells[parameter]) and not hasValue(args[excludeParameter])) then
if (v ~= 'ProdCode') then
createEmptyCell(args, parameter, true)
else
args[parameter] = ''
end
if (title.namespace == 0) then
addTrackingCategory(trackingCategoryList["nonmatching_numbered_parameters"])
end
elseif (hasValue(args[numberedParameter]) and not hasValue(args[excludeNumberParameter])) then
args[parameter] = args[numberedParameter]
end
end
return args
end
--[[
Local function which is used to create the Title cell text.
The title text will be handled in the following way:
Line 1:
<RTitle> (with no space between)</p>
<p>Line 2: <AltTitle><RAltTitle> (with no space between) OR</p>
<p>Line 2: Transliteration: <TranslitTitle> (<Language>: <NativeTitle>)<RAltTitle> (with space between first two parameters)</p>
<p>If <Title> or <RTitle> are empty,</p>
<p>then the values of line 2 will be placed on line 1 instead.</p>
<p>--]]</p>
<p>local function createTitleText(args)</p>
<p>local titleString = ''</p>
<p>local isCellPresent = false</p>
<p>local useSecondLine = false</p>
<p>local lineBreakUsed = false</p>
<p>-- Surround the Title with quotes; No quotes if empty.</p>
<p>if (args.Title ~= nil) then</p>
<p>if (args.Title == "") then</p>
<p>isCellPresent = true</p>
<p>else</p>
<p>titleString = '"' .. args.Title .. '"'</p>
<p>useSecondLine = true</p>
<p>isCellPresent = true</p>
<p>end</p>
<p>end</p>
<p>if (args.RTitle ~= nil) then</p>
<p>if (args.RTitle == "") then</p>
<p>isCellPresent = true</p>
<p>else</p>
<p>titleString = titleString .. args.RTitle</p>
<p>useSecondLine = true</p>
<p>isCellPresent = true</p>
<p>end</p>
<p>end</p>
<p>-- Surround the AltTitle/TranslitTitle with quotes; No quotes if empty.</p>
<p>if (args.AltTitle or args.TranslitTitle) then</p>
<p>isCellPresent = true</p>
<p>if (useSecondLine) then</p>
<p>titleString = titleString .. "<br />"</p>
<p>lineBreakUsed = true</p>
<p>end</p>
<p>if (hasValue(args.AltTitle)) then</p>
<p>titleString = titleString .. '"' .. args.AltTitle .. '"'</p>
<p>elseif (hasValue(args.TranslitTitle)) then</p>
<p>if (hasValue(args.NativeTitleLangCode)) then</p>
<p>titleString = titleString .. 'Transliteration: "' .. langModule._xlit({args.NativeTitleLangCode, args.TranslitTitle, italic = 'no'}) .. '"'</p>
<p>else</p>
<p>titleString = titleString .. 'Transliteration: "' .. args.TranslitTitle .. '"'</p>
<p>end</p>
<p>end</p>
<p>end</p>
<p>if (args.NativeTitle ~= nil) then</p>
<p>if (args.NativeTitle == "") then</p>
<p>isCellPresent = true</p>
<p>else</p>
<p>isCellPresent = true</p>
<p>if (useSecondLine and lineBreakUsed == false) then</p>
<p>titleString = titleString .. "<br />"</p>
<p>end</p>
<p>if (hasValue(args.NativeTitleLangCode)) then</p>
<p>local languageCode = "Lang-" .. args.NativeTitleLangCode</p>
<p>titleString = titleString .. " (" .. langModule._langx({code = args.NativeTitleLangCode, text=args.NativeTitle}) .. ")"</p>
<p>else</p>
<p>titleString = titleString .. " (" .. args.NativeTitle .. ")"</p>
<p>end</p>
<p>end</p>
<p>end</p>
<p>if (args.RAltTitle ~= nil) then</p>
<p>if (args.RAltTitle == "") then</p>
<p>isCellPresent = true</p>
<p>else</p>
<p>isCellPresent = true</p>
<p>if (useSecondLine and lineBreakUsed == false) then</p>
<p>titleString = titleString .. "<br />"</p>
<p>end</p>
<p>titleString = titleString .. args.RAltTitle</p>
<p>end</p>
<p>end</p>
<p>return titleString, isCellPresent</p>
<p>end</p>
<p>--[[</p>
<p>Local function which is used to extract data</p>
<p>from the numbered title parameters (Title1, RTitle2, etc.), and then convert them to</p>
<p>use the non-numbered prameter names (Title, RTitle).</p>
<p>The function returns two results:</p>
<p>-- The args parameter table.</p>
<p>-- A boolean indicating if the title group has data.</p>
<p>]]--</p>
<p>local function extractDataFromNumberedTitleArgs(args, i)</p>
<p>local nextGroupValid = false</p>
<p>for _, v in ipairs(titleList) do</p>
<p>local parameter = v</p>
<p>local numberedParameter = v .. "_" .. i</p>
<p>args[parameter] = args[numberedParameter]</p>
<p>if (nextGroupValid == false and hasValue(args[numberedParameter])) then</p>
<p>nextGroupValid = true</p>
<p>end</p>
<p>end</p>
<p>return args, nextGroupValid</p>
<p>end</p>
<p>-- Local function which is used to create a Title cell.</p>
<p>local function createTitleCell(args, numberOfParameterGroups, currentRow, isSerial)</p>
<p>local titleText</p>
<p>local isCellPresent</p>
<p>if (isSerial and args.Title and currentRow > 1) then</p>
<p>return nil</p>
<p>end</p>
<p>if (args.Title_2) then</p>
<p>local args, nextGroupValid = extractDataFromNumberedTitleArgs(args, currentRow)</p>
<p>end</p>
<p>titleText, isCellPresent = createTitleText(args)</p>
<p>if (isCellPresent == false) then</p>
<p>return nil</p>
<p>end</p>
<p>local textAlign = "left"</p>
<p>-- If Title is blank, then set Raw Title to TBA</p>
<p>if (hasValue(titleText) == false) then</p>
<p>titleText = tableEmptyCellModule._main({})</p>
<p>textAlign = "left"</p>
<p>end</p>
<p>-- If title is the first cell, create it with a !scope="row"</p>
<p>if (nonNilParams == 0) then</p>
<p>if (isSerial) then</p>
<p>row:tag('th')</p>
<p>:addClass('summary')</p>
<p>:attr('scope', 'row')</p>
<p>:attr('rowspan', numberOfParameterGroups)</p>
<p>:css('text-align', textAlign)</p>
<p>:wikitext(titleText)</p>
<p>else</p>
<p>row:tag('th')</p>
<p>:addClass('summary')</p>
<p>:attr('scope', 'row')</p>
<p>:css('text-align', textAlign)</p>
<p>:wikitext(titleText)</p>
<p>end</p>
<p>else</p>
<p>if (isSerial) then</p>
<p>row:tag('td')</p>
<p>:addClass('summary')</p>
<p>:attr('rowspan', numberOfParameterGroups)</p>
<p>:css('text-align', textAlign)</p>
<p>:wikitext(titleText)</p>
<p>else</p>
<p>row:tag('td')</p>
<p>:addClass('summary')</p>
<p>:css('text-align', textAlign)</p>
<p>:wikitext(titleText)</p>
<p>end</p>
<p>end</p>
<p>nonNilParams = nonNilParams + 1</p>
<p>end</p>
<p>--[[</p>
<p>Local function which is used to create column cells.</p>
<p>EpisodeNumber, EpisodeNumber2 are created in different functions</p>
<p>as they require some various if checks.</p>
<p>See:</p>
<p>-- createEpisodeNumberCell()</p>
<p>-- createEpisodeNumberCellSecondary()</p>
<p>]]--</p>
<p>local function createCells(args, isSerial, currentRow, onInitialPage, title, numberOfParameterGroups)</p>
<p>for k, v in ipairs(cellNameList) do</p>
<p>if (v == 'ProdCode') then</p>
<p>if (currentRow == 1 or (currentRow > 1 and parameterGroupCells[v])) then</p>
<p>createProductionCodeCell(args, v, numberOfParameterGroups)</p>
<p>end</p>
<p>elseif (v == 'Title') then</p>
<p>if (currentRow == 1 or (currentRow > 1 and parameterGroupCells[v])) then</p>
<p>local isSerial = not args.Title_2 and true or false</p>
<p>createTitleCell(args, numberOfParameterGroups, currentRow, isSerial)</p>
<p>end</p>
<p>elseif excludeList[v] and args[excludeList[v]] then</p>
<p>-- Ignore this parameter set as multiple conflicting parameters were used</p>
<p>elseif (args[v] and (v ~= 'EpisodeNumber' and v ~= 'EpisodeNumber2')) then</p>
<p>-- Set empty cells to TBA/TBD</p>
<p>if (args[v] == '') then</p>
<p>createEmptyCell(args, v, false)</p>
<p>elseif (v == 'WrittenBy' and title.namespace == 0) then</p>
<p>if ((string.find(args[v], "<em>Story") ~= nil or string.find(args[v], "</em>Teleplay") ~= nil) and string.find(args[v], "8202") == nil) then</p>
<p>-- is the hairspace added through {{StoryTeleplay}}</p>
<p>addTrackingCategory(trackingCategoryList["raw_unformatted_storyteleplay"])</p>
<p>end</p>
<p>end</p>
<p>-- If serial titles need to be centered and not left, then this should be removed.</p>
<p>local textAlign = "center"</p>
<p>if (v == 'Aux1' and isSerial) then</p>
<p>textAlign = "left"</p>
<p>end</p>
<p>local thisRowspan</p>
<p>if (not parameterGroupCells[v] and parameterGroupCellsAny) then</p>
<p>thisRowspan = numberOfParameterGroups</p>
<p>else</p>
<p>thisRowspan = 1</p>
<p>end</p>
<p>if (currentRow == 1 or (currentRow > 1 and parameterGroupCells[v])) then</p>
<p>createTableData(args[v], thisRowspan, textAlign)</p>
<p>end</p>
<p>nonNilParams = nonNilParams + 1</p>
<p>checkUsageOfDateTemplates(args, v, onInitialPage, title)</p>
<p>end</p>
<p>-- Usages of TBA via <a href='?title=Template%3ATableTBA'>Template:TableTBA</a> can be found with the "tv-tba" class.</p>
<p>if args[v] and (args[v] == "TBA" or string.find(args[v], "tv%-tba")) then</p>
<p>cellValueTBA = true</p>
<p>end</p>
<p>end</p>
<p>end</p>
<p>-- Local function which is used to create a table row header for either the</p>
<p>-- EpisodeNumber or EpisodeNumber2 column cells.</p>
<p>local function createTableRowEpisodeNumberHeader(episodeNumber, numberOfParameterGroups, episodeText, separateEpisodeNumbers)</p>
<p>local epID = string.match(episodeNumber, "^%w+")</p>
<p>row:tag('th')</p>
<p>:attr('scope', 'row')</p>
<p>:attr('rowspan', not separateEpisodeNumbers and numberOfParameterGroups or 1)</p>
<p>:attr('id', epID and 'ep' .. epID or '')</p>
<p>:css('text-align', 'center')</p>
<p>:wikitext(episodeText)</p>
<p>end</p>
<p>--[[</p>
<p>Local function which is used to extract the text from the EpisodeNumber or EpisodeNumber2</p>
<p>parameters and format them into a correct MoS compliant version.</p>
<p>Styles supported:</p>
<p>-- A number range of two numbers, indicating the start and end of the range,</p>
<p>seperated by an en-dash (–) with no spaces in between.</p>
<p>Example: "1 - 2" -> "1–2"; "1-2-3" -> "1–3".</p>
<p>-- An alphanumeric or letter range, similar to the above.</p>
<p>Example: "A - B" -> "A–B"; "A-B-C" -> "A–C".</p>
<p>Example: "A1 - B1" -> "A1–B1"; "A1-B1-C1" -> "A1–C1".</p>
<p>-- A number range of two numbers, indicating the start and end of the range,</p>
<p>seperated by a visual <hr /> (divider line).</p>
<p>-- An alphanumeric or letter range, similar to the above.</p>
<p>]]--</p>
<p>local function getEpisodeText(episodeNumber)</p>
<p>if (episodeNumber == '') then</p>
<p>return tableEmptyCellModule._main({})</p>
<p>else</p>
<p>local episodeNumber1</p>
<p>local episodeNumber2</p>
<p>-- Used for double episodes that need a visual "–"" or "<hr />"" added.</p>
<p>local divider</p>
<p>episodeNumber = episodeNumber:gsub('%s*<br%s*/?%s*>%s*', '<hr />')</p>
<p>if (episodeNumber:match('^(%w+)%s*<hr */%s*>%s*(%w+)$')) then</p>
<p>episodeNumber1, episodeNumber2 = episodeNumber:match('^(%w+)%s*<hr */%s*>%s*(%w+)$')</p>
<p>divider = "<hr />"</p>
<p>elseif (episodeNumber:match('^(%w+)%s*<hr */%s*>.-<hr */%s*>%s*(%w+)$')) then -- 3 or more elements</p>
<p>episodeNumber1, episodeNumber2 = episodeNumber:match('^(%w+)%s*<hr */%s*>.-<hr */%s*>%s*(%w+)$')</p>
<p>divider = "<hr />"</p>
<p>elseif (mw.ustring.match(episodeNumber, '^(%w+)%s*[%s%-–/&]%s*(%w+)$')) then</p>
<p>episodeNumber1, episodeNumber2 = mw.ustring.match(episodeNumber, '^(%w+)%s*[%s%-–/&]%s*(%w+)$')</p>
<p>divider = "–"</p>
<p>else</p>
<p>episodeNumber1, episodeNumber2 = mw.ustring.match(episodeNumber, '^(%w+)%s*[%s%-–/&].-[%s%-–/&]%s*(%w+)$') -- 3 or more elements</p>
<p>divider = "–"</p>
<p>end</p>
<p>if (not episodeNumber1) then</p>
<p>return episodeNumber</p>
<p>elseif (not episodeNumber2) then</p>
<p>return string.match(episodeNumber, '%w+')</p>
<p>else</p>
<p>return episodeNumber1 .. divider .. episodeNumber2</p>
<p>end</p>
<p>end</p>
<p>end</p>
<p>-- Local function which is used to create EpisodeNumber2 and EpisodeNumber3 cells.</p>
<p>local function _createEpisodeNumberCellSecondary(episodeValue, numberOfParameterGroups, separateEpisodeNumbers)</p>
<p>if (episodeValue) then</p>
<p>local episodeText = getEpisodeText(episodeValue)</p>
<p>if (nonNilParams == 0) then</p>
<p>createTableRowEpisodeNumberHeader(episodeValue, numberOfParameterGroups, episodeText, separateEpisodeNumbers)</p>
<p>else</p>
<p>createTableData(episodeText, not separateEpisodeNumbers and numberOfParameterGroups or 1, "center")</p>
<p>end</p>
<p>nonNilParams = nonNilParams + 1</p>
<p>end</p>
<p>end</p>
<p>-- Local function which is used to create seconday episode number cells.</p>
<p>local function createEpisodeNumberCellSecondary(args, numberOfParameterGroups, separateEpisodeNumbers)</p>
<p>_createEpisodeNumberCellSecondary(args.EpisodeNumber2, numberOfParameterGroups, separateEpisodeNumbers)</p>
<p>_createEpisodeNumberCellSecondary(args.EpisodeNumber3, numberOfParameterGroups, separateEpisodeNumbers)</p>
<p>end</p>
<p>-- Local function which is used to create an EpisodeNumber cell.</p>
<p>local function createEpisodeNumberCell(args, numberOfParameterGroups, separateEpisodeNumbers)</p>
<p>if (args.EpisodeNumber) then</p>
<p>local episodeText = getEpisodeText(args.EpisodeNumber)</p>
<p>createTableRowEpisodeNumberHeader(args.EpisodeNumber, numberOfParameterGroups, episodeText, separateEpisodeNumbers)</p>
<p>nonNilParams = nonNilParams + 1</p>
<p>end</p>
<p>end</p>
<p>-- Local function which is used to create a single row of cells.</p>
<p>-- This is the standard function called.</p>
<p>local function createSingleRowCells(args, numberOfParameterGroups, multiTitleListEnabled, onInitialPage, title)</p>
<p>createEpisodeNumberCell(args, 1, false)</p>
<p>createEpisodeNumberCellSecondary(args, 1, false)</p>
<p>createCells(args, false, 1, onInitialPage, title, numberOfParameterGroups)</p>
<p>end</p>
<p>-- Local function which is used to create a multiple row of cells.</p>
<p>-- This function is called when part of the row is rowspaned.</p>
<p>local function createMultiRowCells(args, numberOfParameterGroups, onInitialPage, title, topColor)</p>
<p>local EpisodeNumberSplit = (args.EpisodeNumber_1 and true or false)</p>
<p>for i = 1, numberOfParameterGroups do</p>
<p>args = extractDataFromNumberedSerialArgs(args, i, numberOfParameterGroups, title)</p>
<p>if (EpisodeNumberSplit or (not EpisodeNumberSplit and i == 1)) then</p>
<p>createEpisodeNumberCell(args, numberOfParameterGroups, EpisodeNumberSplit)</p>
<p>createEpisodeNumberCellSecondary(args, numberOfParameterGroups, EpisodeNumberSplit)</p>
<p>end</p>
<p>createCells(args, true, i, onInitialPage, title, numberOfParameterGroups)</p>
<p>if (i ~= numberOfParameterGroups) then</p>
<p>local textColor = '#333'</p>
<p>if topColor == 'inherit' then</p>
<p>textColor = 'inherit'</p>
<p>end</p>
<p>row = row:done() -- Use done() to close the 'tr' tag in rowspaned rows.</p>
<p>:tag('tr')</p>
<p>:addClass('vevent')</p>
<p>:addClass('module-episode-list-row')</p>
<p>:css('text-align', 'center')</p>
<p>:css('background', topColor)</p>
<p>:css('color', textColor)</p>
<p>end</p>
<p>end</p>
<p>end</p>
<p>-- Local function which is used to retrieve the NumParts value.</p>
<p>local function getnumberOfParameterGroups(args)</p>
<p>for k, v in ipairs(cellNameList) do</p>
<p>local numberedParameter = v .. "_" .. 1</p>
<p>if (args[numberedParameter]) then</p>
<p>parameterGroupCells[v] = true</p>
<p>parameterGroupCellsAny = true</p>
<p>if not firstParameterGroupCell then</p>
<p>firstParameterGroupCell = k</p>
<p>end</p>
<p>end</p>
<p>end</p>
<p>if (hasValue(args.NumParts)) then</p>
<p>return args.NumParts, true</p>
<p>else</p>
<p>return 1, false</p>
<p>end</p>
<p>end</p>
<p>-- Local function which is used to retrieve the Top Color value.</p>
<p>local function getTopColor(args, rowColorEnabled, onInitialPage)</p>
<p>local episodeNumber = mathModule._cleanNumber(args.EpisodeNumber) or 1</p>
<p>if (args.TopColor) then</p>
<p>if (string.find(args.TopColor, "#")) then</p>
<p>return args.TopColor</p>
<p>else</p>
<p>return '#' .. args.TopColor</p>
<p>end</p>
<p>else</p>
<p>return 'inherit'</p>
<p>end</p>
<p>end</p>
<p>-- Local function which is used to retrieve the Row Color value.</p>
<p>local function isRowColorEnabled(args)</p>
<p>local rowColorEnabled = yesNoModule(args.RowColor, false)</p>
<p>if (args.RowColor and string.lower(args.RowColor) == 'on') then</p>
<p>rowColorEnabled = true</p>
<p>end</p>
<p>return rowColorEnabled</p>
<p>end</p>
<p>-- Local function which is used to retrieve the Line Color value.</p>
<p>local function getLineColor(args)</p>
<p>-- Default color to light blue</p>
<p>local lineColor = args.LineColor or 'CCCCFF'</p>
<p>-- Add # to color if necessary, and set to default color if invalid</p>
<p>if (htmlColor[lineColor] == nil) then</p>
<p>lineColor = '#' .. (mw.ustring.match(lineColor, '^[%s#]*([a-fA-F0-9]*)[%s]*$') or '')</p>
<p>if (lineColor == '#') then</p>
<p>lineColor = '#CCCCFF'</p>
<p>end</p>
<p>end</p>
<p>return lineColor</p>
<p>end</p>
<p>-- Local function which is used to check if the table is located on the page</p>
<p>-- currently viewed, or on a transcluded page instead.</p>
<p>-- If it is on a transcluded page, the episode summary should not be shown.</p>
<p>local function isOnInitialPage(args, sublist, pageTitle, initiallistTitle)</p>
<p>-- This should be the only check needed, however, it was previously implemented with two templates</p>
<p>-- with one of them not requiring an article name, so for backward compatability, the whole sequence is kept.</p>
<p>local onInitialPage</p>
<p>local onInitialPageCheck = (mw.uri.anchorEncode(pageTitle) == mw.uri.anchorEncode(initiallistTitle))</p>
<p>-- Only sublist had anything about hiding, so only it needs to even check</p>
<p>if (sublist) then</p>
<p>onInitialPage = onInitialPageCheck</p>
<p>-- avoid processing ghost references</p>
<p>if (not onInitialPage) then</p>
<p>args.ShortSummary = nil</p>
<p>end</p>
<p>else</p>
<p>if (initiallistTitle == "") then</p>
<p>onInitialPage = true</p>
<p>else</p>
<p>onInitialPage = onInitialPageCheck</p>
<p>end</p>
<p>end</p>
<p>return onInitialPage</p>
<p>end</p>
<p>-- Local function which does the actual main process.</p>
<p>local function _main(args, sublist)</p>
<p>local title = mw.title.getCurrentTitle()</p>
<p>local pageTitle = title.text</p>
<p>local initiallistTitle = args['1'] or ''</p>
<p>-- Is this list on the same page as the page directly calling the template?</p>
<p>local onInitialPage = isOnInitialPage(args, sublist, pageTitle, initiallistTitle)</p>
<p>-- Need just this parameter removed if blank, no others</p>
<p>if (hasValue(args.ShortSummary) == false) then</p>
<p>args.ShortSummary = nil</p>
<p>end</p>
<p>local lineColor = getLineColor(args)</p>
<p>local rowColorEnabled = isRowColorEnabled(args)</p>
<p>local topColor = getTopColor(args, rowColorEnabled, onInitialPage)</p>
<p>local root = mw.html.create() -- Create the root mw.html object to return</p>
<p>local textColor = '#333'</p>
<p>if topColor == 'inherit' then</p>
<p>textColor = 'inherit'</p>
<p>end</p>
<p>row = root:tag('tr') -- Create the table row and store it globally</p>
<p>:addClass('vevent')</p>
<p>:addClass('module-episode-list-row')</p>
<p>:css('text-align', 'center')</p>
<p>:css('background', topColor)</p>
<p>:css('color', textColor)</p>
<p>local numberOfParameterGroups, multiTitleListEnabled = getnumberOfParameterGroups(args)</p>
<p>if (multiTitleListEnabled) then</p>
<p>createMultiRowCells(args, numberOfParameterGroups, onInitialPage, title, topColor)</p>
<p>else</p>
<p>createSingleRowCells(args, numberOfParameterGroups, multiTitleListEnabled, onInitialPage, title)</p>
<p>end</p>
<p>-- add these categories only in the mainspace and only if they are on the page where the template is used</p>
<p>if (onInitialPage and title.namespace == 0) then</p>
<p>addLineColorTrackingCategories(args)</p>
<p>addTopColorTrackingCategories(args)</p>
<p>end</p>
<p>if (cellValueTBA == true and title.namespace == 0) then</p>
<p>addTrackingCategory(trackingCategoryList["tba_values"])</p>
<p>end</p>
<p>-- Do not show the summary if this is being transcluded on the initial list page</p>
<p>-- Do include it on all other lists</p>
<p>if (onInitialPage and args.ShortSummary) then</p>
<p>local bottomWrapper = createShortSummaryRow(args, lineColor)</p>
<p>return tostring(root) .. tostring(bottomWrapper) .. trackingCategories</p>
<p>else</p>
<p>return tostring(root) .. trackingCategories</p>
<p>end</p>
<p>end</p>
<p>-- Local function which handles both module entry points.</p>
<p>local function main(frame, sublist)</p>
<p>local getArgs = require('Module:Arguments').getArgs</p>
<p>local args</p>
<p>-- Most parameters should still display when blank, so don't remove blanks</p>
<p>if (sublist) then</p>
<p>args = getArgs(frame, {removeBlanks = false, wrappers = 'Template:Episode list/sublist'})</p>
<p>else</p>
<p>args = getArgs(frame, {removeBlanks = false, wrappers = 'Template:Episode list'})</p>
<p>end</p>
<p>-- args['1'] = mw.getCurrentFrame():getParent():getTitle()</p>
<p>return _main(args, sublist, frame)</p>
<p>end</p>
<p>--[[</p>
<p>Public function which is used to create an Episode row</p>
<p>for an Episode Table used for lists of episodes where each table is on a different page,</p>
<p>usually placed on individual season articles.</p>
<p>For tables which are all on the same page see p.list().</p>
<p>Parameters:</p>
<p>-- |1= — required; The title of the article where the Episode Table is located at.</p>
<p>-- |EpisodeNumber= — suggested; The overall episode number in the series.</p>
<p>-- |EpisodeNumber2= — suggested; The episode number in the season.</p>
<p>-- |Title= — suggested; The English title of the episode.</p>
<p>-- |RTitle= — optional; Unformatted parameter that can be used to add a reference after "Title",</p>
<p>or can be used as a "raw title" to replace "Title" completely.</p>
<p>-- |AltTitle= — optional; An alternative title, such as the title of a foreign show's episode in its native language,</p>
<p>or a title that was originally changed.</p>
<p>-- |TranslitTitle= — optional; The title of the episode transliteration (Romanization) to Latin characters.</p>
<p>-- |RAltTitle= — optional; Unformatted parameter that can be used to add a reference after "AltTitle",</p>
<p>or can be used as a "raw title" to replace "AltTitle" completely.</p>
<p>-- |NativeTitle= — optional; The title of the episode in the native language.</p>
<p>-- |NativeTitleLangCode — optional; The language code of the native title language.</p>
<p>-- |Aux1= — optional; General purpose parameter. The meaning is specified by the column header.</p>
<p>This parameter is also used for Serial episode titles, such as those used in Doctor Who.</p>
<p>-- |DirectedBy= — optional; Name of the episode's director. May contain links.</p>
<p>-- |WrittenBy= — optional; Primary writer(s) of the episode. May include links.</p>
<p>-- |Aux2= — optional; General purpose parameter. The meaning is specified by the column header.</p>
<p>-- |Aux3= — optional; General purpose parameter. The meaning is specified by the column header.</p>
<p>-- |OriginalAirDate= — optional; This is the date the episode first aired on TV, or is scheduled to air.</p>
<p>-- |AltDate= — optional; The next notable air date, such as the first air date of an anime in English.</p>
<p>-- |Guests= — optional; List of Guests for talk shows. Cannot be used simultaneously with Aux1.</p>
<p>-- |MusicalGuests= — optional; List of MusicalGuests for talk shows. Cannot be used simultaneously with Aux2.</p>
<p>-- |ProdCode= — optional; The production code in the series. When defined, this parameter also creates a link anchor,</p>
<p>prefixed by "pc"; for example, List of episodes#pc01.</p>
<p>-- |Viewers= — optional; Number of viewers who watched the episode. Should include a reference.</p>
<p>-- |Aux4= — optional; General purpose parameter. The meaning is specified by the column header.</p>
<p>-- |ShortSummary= — optional; A short 100–200 word plot summary of the episode.</p>
<p>-- |LineColor= — optional; Colors the separator line between episode entries. If not defined the color defaults to "#CCCCFF"</p>
<p>and the article is placed in Category:Episode list using the default LineColor.</p>
<p>Use of "#", or anything but a valid hex code will result in an invalid syntax.</p>
<p>-- |TopColor= — discouraged; Colors the main row of information (that is, not the ShortSummary row).</p>
<p>Articles using this parameter are placed in Category:Episode lists with row deviations.</p>
<p>-- |RowColor= — optional; Switch parameter that must only be defined when the EpisodeNumber= entry is not a regular number</p>
<p>(e.g. "12–13" for two episodes described in one table entry).</p>
<p>If the first episode number is even, define pass "on". If the first episode number is odd, pass "off".</p>
<p>--]]</p>
<p>function p.sublist(frame)</p>
<p>return main(frame, true)</p>
<p>end</p>
<p>--[[</p>
<p>Public function which is used to create an Episode row</p>
<p>for an Episode Table used for lists of episodes where all tables are on the same page.</p>
<p>For tables which are on different pages see p.sublist().</p>
<p>For complete parameter documentation, see the documentation at p.sublist().</p>
<p>--]]</p>
<p>function p.list(frame)</p>
<p>return main(frame, false)</p>
<p>end</p>
<p>return p</p></div></section></div></main>
<footer class="site-footer">
<div class="footer-container">
<div class="footer-links">
<a href="/about.php">About</a>
<a href="/help.php">Help</a>
<a href="/updates.php">Updates</a>
<a href="/contact.php">Contact</a>
<a href="/privacy.php">Privacy</a>
<a href="/terms.php">Terms</a>
<a href="https://github.com/yourusername/friendly-wiki" target="_blank" rel="noopener">GitHub</a>
</div>
<div class="footer-copy">
© 2025 Friendly Wiki. All rights reserved.
</div>
</div>
</footer>
<script>
const toggle = document.getElementById('mobileMenuToggle');
const menu = document.getElementById('mobileMenu');
toggle.addEventListener('click', () => {
menu.classList.toggle('active');
});
</script>
<!-- Collapsible toggle -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const toggles = document.querySelectorAll('.section-toggle');
toggles.forEach(toggle => {
toggle.addEventListener('click', function () {
const section = toggle.closest('.collapsible');
const body = section.querySelector('.wiki-body');
body.classList.toggle('collapsed');
});
});
});
</script>