Module:Sandbox/LVL: Difference between revisions

Jump to navigation Jump to search
LVL (talk | contribs)
No edit summary
LVL (talk | contribs)
No edit summary
Line 1: Line 1:
-- Single-file Tabs module for deadlock.wiki
-- @Liquipedia
-- All dependencies inlined to avoid Lua.import
-- page=Module:TabsCombined
-- Combined version of Module:Tabs with all dependencies
local Tabs = {}
 
--[[
=================================================================
Start of dependency: Module:Table
=================================================================
]]
local Table = {}


-- Inlined Module:Array (partial stub - expand if needed)
function Table.size(tbl)
local Array = {}
local i = 0
Array.forEach = function(elements, funct)
for _ in pairs(tbl) do
for index, element in ipairs(elements) do
i = i + 1
funct(element, index)
end
end
return i
end
end
Array.any = function(tbl, predicate)
 
for _, v in ipairs(tbl) do
function Table.includes(tbl, value, isPattern)
if predicate(v) then return true end
for _, entry in pairs(tbl) do
if isPattern and string.find(entry, value)
or not isPattern and entry == value then
return true
end
end
end
return false
return false
end
end
Array.all = function(tbl, predicate)
 
for _, v in ipairs(tbl) do
function Table.getKeyOfValue(tbl, value)
if not predicate(v) then return false end
for key, entry in pairs(tbl) do
if entry == value then
return key
end
end
return nil
end
 
function Table.filter(tbl, predicate, argument)
local filteredTbl = {}
local foundMatches = 1
 
for _, entry in pairs(tbl) do
if predicate(entry, argument) then
filteredTbl[foundMatches] = entry
foundMatches = foundMatches + 1
end
end
 
return filteredTbl
end
 
function Table.filterByKey(tbl, predicate)
local filteredTbl = {}
 
for key, entry in pairs(tbl) do
if predicate(key, entry) then
filteredTbl[key] = entry
end
end
 
return filteredTbl
end
 
function Table.isEmpty(tbl)
if tbl == nil then
return true
end
for _, _ in pairs(tbl) do
return false
end
end
return true
return true
end
end


-- Inlined Module:Class
function Table.isNotEmpty(tbl)
local Class = {}
return not Table.isEmpty(tbl)
function Class.export(tbl, opts)
end
 
function Table.copy(tbl)
local result = {}
local result = {}
for _, name in ipairs(opts.exports) do
for key, entry in pairs(tbl) do
result[name] = tbl[name]
result[key] = entry
end
end
return result
return result
end
end


-- Inlined Module:Logic
function Table.deepCopy(tbl_, options)
options = options or {}
assert(type(tbl_) == 'table', 'Table.deepCopy: Input must be a table')
 
local function deepCopy(tbl)
local result = {}
for key, value in pairs(tbl) do
result[key] = type(value) == 'table' and deepCopy(value) or value
end
if options.copyMetatable then
local metatable = getmetatable(tbl)
if type(metatable) == 'table' then
setmetatable(result, deepCopy(metatable))
end
end
return result
end
 
return deepCopy(tbl_)
end
 
function Table.deepEquals(xTable, yTable)
assert(type(xTable) == 'table', 'Table.deepEquals: First argument must be a table')
assert(type(yTable) == 'table', 'Table.deepEquals: Second argument must be a table')
 
for key, value in pairs(xTable) do
if not Logic.deepEquals(value, yTable[key]) then
return false
end
end
 
for key, _ in pairs(yTable) do
if xTable[key] == nil then
return false
end
end
 
return true
end
 
function Table.mergeInto(target, ...)
local objs = Table.pack(...)
for i = 1, objs.n do
if type(objs[i]) == 'table' then
for key, value in pairs(objs[i]) do
target[key] = value
end
end
end
return target
end
 
function Table.merge(...)
return Table.mergeInto({}, ...)
end
 
function Table.deepMergeInto(target, ...)
local tbls = Table.pack(...)
for i = 1, tbls.n do
if type(tbls[i]) == 'table' then
for key, value in pairs(tbls[i]) do
if type(target[key]) == 'table' and type(value) == 'table' then
Table.deepMergeInto(target[key], value)
else
target[key] = value
end
end
end
end
return target
end
 
function Table.deepMerge(...)
return Table.deepMergeInto({}, ...)
end
 
function Table.map(xTable, f)
local yTable = {}
for xKey, xValue in pairs(xTable) do
local yKey, yValue = f(xKey, xValue)
yTable[yKey] = yValue
end
return yTable
end
 
function Table.mapArgumentsByPrefix(args, prefixes, f, noInterleave)
local function indexFromKey(key)
local prefix, index = key:match('^([%a_]+)(%d+)$')
if Table.includes(prefixes, prefix) then
return tonumber(index), prefix
else
return nil
end
end
 
return Table.mapArguments(args, indexFromKey, f, noInterleave)
end
 
function Table.mapArguments(args, indexFromKey, f, noInterleave)
local entriesByIndex = {}
 
-- Non-numeric args
for key, _ in pairs(args) do
local function post(index, ...)
if index and not entriesByIndex[index] then
entriesByIndex[index] = f(key, index, ...)
end
end
if type(key) == 'string' then
post(indexFromKey(key))
end
end
 
if noInterleave then
return entriesByIndex
end
 
-- Numeric index entries fills in gaps of prefixN= entries if not disabled
local entryIndex = 1
for argIndex = 1, math.huge do
if not args[argIndex] then
break
end
while entriesByIndex[entryIndex] do
entryIndex = entryIndex + 1
end
entriesByIndex[entryIndex] = f(argIndex, entryIndex)
end
 
return entriesByIndex
end
 
function Table.mapValues(xTable, f)
local yTable = {}
for xKey, xValue in pairs(xTable) do
yTable[xKey] = f(xValue)
end
return yTable
end
 
function Table.all(tbl, predicate)
for key, value in pairs(tbl) do
if not predicate(key, value) then
return false
end
end
return true
end
 
function Table.any(tbl, predicate)
for key, value in pairs(tbl) do
if predicate(key, value) then
return true
end
end
return false
end
 
function Table.groupBy(tbl, f)
local groups = {}
for key, value in pairs(tbl) do
local groupKey = f(key, value)
if not groups[groupKey] then
groups[groupKey] = {}
end
groups[groupKey][key] = value
end
return groups
end
 
function Table.extract(tbl, key)
local value = tbl[key]
tbl[key] = nil
return value
end
 
function Table.getByPathOrNil(tbl, path)
for _, fieldName in ipairs(path) do
if type(tbl) ~= 'table' then
return nil
end
tbl = tbl[fieldName]
end
return tbl
end
 
function Table.setByPath(tbl, path, value)
for i = 1, #path - 1 do
if tbl[path[i]] == nil then
tbl[path[i]] = {}
end
tbl = tbl[path[i]]
end
tbl[path[#path]] = value
end
 
function Table.uniqueKey(tbl)
local key0 = nil
for key, _ in pairs(tbl) do
if key0 ~= nil then return nil end
key0 = key
end
return key0
end
 
function Table.entries(tbl)
local entries = {}
for key, value in pairs(tbl) do
table.insert(entries, {key, value})
end
return entries
end
 
function Table.pack(...)
return {n = select('#', ...), ...}
end
 
Table.iter = {}
 
function Table.iter.spairs(tbl, order)
local keys = {}
for k in pairs(tbl) do keys[#keys+1] = k end
 
if order then
table.sort(keys, function(a,b) return order(tbl, a, b) end)
else
table.sort(keys)
end
 
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], tbl[keys[i]]
end
end
end
 
function Table.iter.pairsByPrefix(tbl, prefixes, options)
options = options or {}
if type(prefixes) == 'string' then
prefixes = {prefixes}
end
 
local getByPrefixes = function(index)
for _, prefix in ipairs(prefixes) do
local key = prefix .. index
if tbl[key] then
return key, tbl[key]
end
end
end
 
local i = 1
return function()
local key, value = getByPrefixes(i)
if options.requireIndex == false and i == 1 and not value then
key, value = getByPrefixes('')
end
i = i + 1
if value then
return key, value, (i - 1)
else
return nil
end
end
end
 
--[[
=================================================================
Start of dependency: Module:Logic
=================================================================
]]
local Logic = {}
local Logic = {}
function Logic.emptyOr(val1, val2, default)
if not Logic.isEmpty(val1) then
return val1
elseif not Logic.isEmpty(val2) then
return val2
else
return default
end
end
function Logic.nilOr(...)
local args = Table.pack(...)
for i = 1, args.n do
local arg = args[i]
local val
if type(arg) == 'function' then
val = arg()
else
val = arg
end
if val ~= nil then
return val
end
end
return nil
end
function Logic.isEmpty(val)
if type(val) == 'table' then
return Table.isEmpty(val)
else
return val == '' or val == nil
end
end
function Logic.isNotEmpty(val)
if type(val) == 'table' then
return Table.isNotEmpty(val)
else
return val ~= nil and val ~= ''
end
end
function Logic.nilIfEmpty(val)
return Logic.isNotEmpty(val) and val or nil
end
function Logic.isDeepEmpty(val)
return Logic.isEmpty(val) or type(val) == 'table' and
Table.all(val, function(key, item) return Logic.isDeepEmpty(item) end)
end
function Logic.isNotDeepEmpty(val)
return not Logic.isDeepEmpty(val)
end
function Logic.readBool(val)
function Logic.readBool(val)
return val == true or val == 'true' or val == '1'
return val == 'true' or val == 't' or val == 'yes' or val == 'y' or val == true or val == '1' or val == 1
end
end
function Logic.readBoolOrNil(val)
function Logic.readBoolOrNil(val)
if val == nil then return nil end
if Logic.readBool(val) then
return Logic.readBool(val)
return true
elseif val == 'false' or val == 'f' or val == 'no' or val == 'n' or val == false or val == '0' or val == 0 then
return false
else
return nil
end
end
 
function Logic.nilThrows(val)
if val == nil then
error('Unexpected nil', 2)
end
return val
end
 
function Logic.tryCatch(try, catch)
local ran, result = pcall(try)
if not ran then
catch(result)
else
return result
end
end
 
function Logic.deepEquals(x, y)
if x == y then
return true
elseif type(x) == 'table' and type(y) == 'table' then
return Table.deepEquals(x, y)
else
return false
end
end
 
--[[
=================================================================
Start of dependency: Module:Array
=================================================================
]]
local Array = {}
 
function Array.randomize(tbl)
math.randomseed(os.time())
for i = #tbl, 2, -1 do
local j = math.random(i)
tbl[i], tbl[j] = tbl[j], tbl[i]
end
return tbl
end
 
function Array.isArray(tbl)
return type(tbl) == 'table' and Table.size(tbl) == #tbl
end
 
function Array.copy(tbl)
local copy = {}
for _, element in ipairs(tbl) do
table.insert(copy, element)
end
return copy
end
 
function Array.sub(tbl, startIndex, endIndex)
if startIndex < 0 then startIndex = #tbl + 1 + startIndex end
if not endIndex then endIndex = #tbl end
if endIndex < 0 then endIndex = #tbl + 1 + endIndex end
 
local subArray = {}
for index = startIndex, endIndex do
table.insert(subArray, tbl[index])
end
return subArray
end
 
function Array.map(elements, funct)
local mappedArray = {}
for index, element in ipairs(elements) do
local mappedElement = funct(element, index)
table.insert(mappedArray, mappedElement)
end
return mappedArray
end
end
function Logic.isEmpty(val)
 
return val == nil or val == ''
function Array.filter(tbl, predicate)
local filteredArray = {}
for index, element in ipairs(tbl) do
if predicate(element, index) then
table.insert(filteredArray, element)
end
end
return filteredArray
end
end
function Logic.isNotEmpty(val)
 
return not Logic.isEmpty(val)
function Array.flatten(tbl)
local flattenedArray = {}
for _, x in ipairs(tbl) do
if type(x) == 'table' then
for _, y in ipairs(x) do
table.insert(flattenedArray, y)
end
else
table.insert(flattenedArray, x)
end
end
return flattenedArray
end
end
function Logic.nilOr(val, fallback)
 
if val == nil then return fallback else return val end
function Array.flatMap(elements, funct)
return Array.flatten(Array.map(elements, funct))
end
end


-- Inlined Module:Operator
function Array.all(tbl, predicate)
local Operator = {}
for _, element in ipairs(tbl) do
function Operator.property(prop)
if not predicate(element) then
return function(tbl)
return false
return tbl and tbl[prop]
end
end
end
return true
end
end


-- Inlined Module:Page
function Array.any(tbl, predicate)
local Page = {}
for _, element in ipairs(tbl) do
function Page.makeInternalLink(_, text, link)
if predicate(element) then
return string.format('[[%s|%s]]', link, text)
return true
end
end
return false
end
end


-- Inlined Module:Table
function Array.find(tbl, predicate)
local Table = {}
for index, element in ipairs(tbl) do
function Table.extract(tbl, key)
if predicate(element, index) then
local val = tbl[key]
return element
if val ~= '' then return val end
end
end
return nil
return nil
end
end


-- Begin Tabs Module
function Array.groupBy(tbl, funct)
local Tabs = {}
local groupsByKey = {}
local groups = {}
for _, xValue in ipairs(tbl) do
local yValue = funct(xValue)
if yValue then
local group = groupsByKey[yValue]
if not group then
group = {}
groupsByKey[yValue] = group
table.insert(groups, group)
end
table.insert(group, xValue)
end
end
return groups, groupsByKey
end
 
function Array.groupAdjacentBy(array, f, equals)
equals = equals or Logic.deepEquals
local groups = {}
local currentKey
for index, elem in ipairs(array) do
local key = f(elem)
if index == 1 or not equals(key, currentKey) then
currentKey = key
table.insert(groups, {})
end
table.insert(groups[#groups], elem)
end
return groups
end
 
function Array.lexicalCompare(tblX, tblY)
for index = 1, math.min(#tblX, #tblY) do
if tblX[index] < tblY[index] then
return true
elseif tblX[index] > tblY[index] then
return false
end
end
return #tblX < #tblY
end
 
function Array.lexicalCompareIfTable(y1, y2)
if type(y1) == 'table' and type(y2) == 'table' then
return Array.lexicalCompare(y1, y2)
else
return y1 < y2
end
end
 
function Array.sortBy(tbl, funct, compare)
local copy = Table.copy(tbl)
Array.sortInPlaceBy(copy, funct, compare)
return copy
end
 
function Array.sortInPlaceBy(tbl, funct, compare)
compare = compare or Array.lexicalCompareIfTable
table.sort(tbl, function(x1, x2) return compare(funct(x1), funct(x2)) end)
end
 
function Array.reverse(tbl)
local reversedArray = {}
for index = #tbl, 1, -1 do
table.insert(reversedArray, tbl[index])
end
return reversedArray
end
 
function Array.append(tbl, ...)
return Array.appendWith(Array.copy(tbl), ...)
end
 
function Array.appendWith(tbl, ...)
local elements = Table.pack(...)
for index = 1, elements.n do
if elements[index] ~= nil then
table.insert(tbl, elements[index])
end
end
return tbl
end
 
function Array.extend(tbl, ...)
return Array.extendWith({}, tbl, ...)
end
 
function Array.extendWith(tbl, ...)
local arrays = Table.pack(...)
for index = 1, arrays.n do
if type(arrays[index]) == 'table' then
for _, element in ipairs(arrays[index]) do
table.insert(tbl, element)
end
elseif arrays[index] ~= nil then
table.insert(tbl, arrays[index])
end
end
return tbl
end
 
function Array.mapIndexes(funct)
local arr = {}
for index = 1, math.huge do
local y = funct(index)
if y then
table.insert(arr, y)
else
break
end
end
return arr
end
 
function Array.range(from, to)
local elements = {}
for element = from, to do
table.insert(elements, element)
end
return elements
end


function Tabs.static(args)
function Array.extractKeys(tbl, iterator, ...)
args = args or {}
iterator = iterator or pairs
local tabArgs = Tabs._readArguments(args, {allowThis2 = true})
local array = {}
local tabCount = #tabArgs
for key, _ in iterator(tbl, ...) do
if tabCount == 0 then return end
table.insert(array, key)
end
return array
end


Tabs._setThis(tabArgs)
function Array.extractValues(tbl, iterator, ...)
local tabs = mw.html.create('ul')
iterator = iterator or pairs
:attr('class', 'nav nav-tabs navigation-not-searchable tabs tabs' .. tabCount)
local array = {}
:attr('data-nosnippet')
for _, item in iterator(tbl, ...) do
table.insert(array, item)
end
return array
end


local subTabs = mw.html.create()
function Array.forEach(elements, funct)
for index, element in ipairs(elements) do
funct(element, index)
end
end


Array.forEach(tabArgs, function(tab)
function Array.reduce(array, operator, initialValue)
local name = tab.name or Tabs._getDisplayNameFromLink(tab.link)
local aggregate
local text = tab.link and Page.makeInternalLink({}, name, tab.link) or tab.name
if initialValue ~= nil then
tabs:tag('li'):addClass(tab.this and 'active' or nil):wikitext(text)
aggregate = initialValue
subTabs:node(tab.this and tab.tabs or nil)
else
end)
aggregate = array[1]
end


return mw.html.create()
for index = initialValue ~= nil and 1 or 2, #array do
:tag('div')
aggregate = operator(aggregate, array[index])
:addClass('tabs-static')
end
:attr('data-nosnippet', '')
return aggregate
:node(tabs)
:done()
:node(subTabs)
end
end


function Tabs.dynamic(args)
function Array.maxBy(array, funct, compare)
args = args or {}
compare = compare or Array.lexicalCompareIfTable
local tabArgs = Tabs._readArguments(args, {removeEmptyTabs = Logic.readBool(args.removeEmptyTabs)})
local max, maxScore
local tabCount = #tabArgs
for _, item in ipairs(array) do
if tabCount == 0 then return end
local score = funct(item)
if max == nil or compare(maxScore, score) then
max = item
maxScore = score
end
end
return max
end


local hasContent = Array.all(tabArgs, function(tab) return Logic.isNotEmpty(tab.content) end)
function Array.max(array, compare)
local allEmpty = Array.all(tabArgs, function(tab) return Logic.isEmpty(tab.content) end)
return Array.maxBy(array, function(x) return x end, compare)
assert(hasContent or allEmpty, 'Some of the tabs have contents while others do not')
end


local isSingular = tabCount == 1 and hasContent
function Array.minBy(array, funct, compare)
if isSingular and not Logic.readBool(args.showSingularAsTab) then
compare = compare or Array.lexicalCompareIfTable
return Tabs._single(tabArgs[1], not Logic.readBool(args.suppressHeader))
local min, minScore
for _, item in ipairs(array) do
local score = funct(item)
if min == nil or compare(score, minScore) then
min = item
minScore = score
end
end
end
return min
end
function Array.min(array, compare)
return Array.minBy(array, function(x) return x end, compare)
end


local tabs = mw.html.create('ul'):addClass('nav nav-tabs tabs tabs' .. tabCount)
function Array.indexOf(array, pred)
if not Array.any(tabArgs, Operator.property('this')) then
for ix, elem in ipairs(array) do
tabArgs[1].this = true
if pred(elem, ix) then
return ix
end
end
end
return 0
end


local build = function(obj, elementType, content, class, isActive)
function Array.unique(elements)
local element = mw.html.create(elementType)
local elementCache = {}
:addClass(class)
local uniqueElements = {}
:addClass(isActive and 'active' or nil)
for _, element in ipairs(elements) do
:newline()
if elementCache[element] == nil then
:node(content)
table.insert(uniqueElements, element)
obj:newline():node(element)
elementCache[element] = true
end
end
end
return uniqueElements
end
function Array.parseCommaSeparatedString(inputString, sep)
if Logic.isEmpty(inputString) then return {} end
return Array.map(mw.text.split(inputString, sep or ','), mw.text.trim)
end


Array.forEach(tabArgs, function(tabData, tabIndex)
function Array.interleave(elements, x)
build(tabs, 'li', tabData.name, 'tab' .. tabIndex, tabData.this)
local size = #elements
return Array.flatMap(elements, function(element, index)
if index == size then
return {element}
end
return {element, x}
end)
end)
end


if not Logic.nilOr(Logic.readBoolOrNil(args['hide-showall']), isSingular) then
--[[
tabs:tag('li'):addClass('show-all'):wikitext('Show All')
=================================================================
Start of dependency: Module:Operator
=================================================================
]]
local Operator = {}
 
function Operator.add(a, b) return a + b end
function Operator.sub(a, b) return a - b end
function Operator.mul(a, b) return a * b end
function Operator.div(a, b) return a / b end
function Operator.pow(a, b) return a ^ b end
function Operator.eq(a, b) return a == b end
function Operator.neq(a, b) return a ~= b end
function Operator.lt(a, b) return a < b end
function Operator.le(a, b) return a <= b end
function Operator.gt(a, b) return a > b end
function Operator.ge(a, b) return a >= b end
 
function Operator.property(item)
assert(type(item) == 'string' or type(item) == 'number', 'Invalid or missing input to `Operator.property`')
local pathSegments = mw.text.split(item, '.', true)
return function(tbl)
local selected = tbl
for segmentIndex, pathSegment in ipairs(pathSegments) do
if type(selected) ~= 'table' and segmentIndex == 1 then
error('Nil supplied to `Operator.property(' .. item .. ')`')
elseif type(selected) ~= 'table' then
local pathUntilHere = Array.sub(pathSegments, 1, segmentIndex - 1)
error('Could not index "tbl.' .. table.concat(pathUntilHere, '.') .. '"')
end
selected = selected[pathSegment] or selected[tonumber(pathSegment)]
end
return selected
end
end
 
function Operator.method(funcName, ...)
local args = {...}
return function(obj)
return obj[funcName](obj, unpack(args))
end
end
end
--[[
=================================================================
Start of dependency: Module:Page
=================================================================
]]
local Page = {}


tabs:newline()
function Page.exists(link)
local existingPage = mw.title.new(link)
if existingPage == nil then
return false
end
return existingPage.exists
end


local contents = Tabs._buildContentDiv(hasContent, Logic.readBool(args['hybrid-tabs']), Logic.readBool(args['no-padding']))
function Page.makeInternalLink(options, display, customLink)
if type(options) == 'string' then
customLink = display
display = options
end
if Logic.isEmpty(display) then
return nil
elseif Logic.isEmpty(customLink) then
customLink = display
end


if not hasContent then
if (options or {}).onlyIfExists == true and (not Page.exists(customLink)) then
return '<div class="tabs-dynamic navigation-not-searchable" data-nosnippet>\n' .. tostring(tabs) .. contents
return nil
end
end


Array.forEach(tabArgs, function(tabData, tabIndex)
return '[[' .. customLink .. '|' .. display .. ']]'
build(contents, 'div', tabData.content, 'content' .. tabIndex, tabData.this)
end
end)
 
function Page.makeExternalLink(display, link)
if Logic.isEmpty(display) or Logic.isEmpty(link) then
return nil
end
return '[' .. link .. ' ' .. display .. ']'
end


return mw.html.create('div')
function Page.pageifyLink(link)
:addClass('tabs-dynamic navigation-not-searchable')
if Logic.isEmpty(link) then
:attr('data-nosnippet')
return nil
:node(tabs)
end
:newline()
return (mw.ext.TeamLiquidIntegration.resolve_redirect(link):gsub(' ', '_'))
:node(contents)
end
end


--[[
=================================================================
Start of original Module:Tabs code
=================================================================
]]
function Tabs._readArguments(args, options)
function Tabs._readArguments(args, options)
local tabArgs = {}
local tabArgs = {}
Line 186: Line 913:


assert(Logic.isNotEmpty(tabArgs), 'You are trying to add a "Tabs" template without arguments for names nor links')
assert(Logic.isNotEmpty(tabArgs), 'You are trying to add a "Tabs" template without arguments for names nor links')
return tabArgs
return tabArgs
end
end
Line 200: Line 928:
if not link then return end
if not link then return end
link = link:gsub('_', ' ')
link = link:gsub('_', ' ')
local linkLength = #link
local linkLength = string.len(link)
local charAfter = fullPageName:sub(linkLength + 1, linkLength + 1)
local charAfter = string.sub(fullPageName, linkLength + 1, linkLength + 1)
local pagePartial = fullPageName:sub(1, linkLength)
local pagePartial = string.sub(fullPageName, 1, linkLength)
if pagePartial == link and (charAfter == '/' or charAfter == '') and linkLength > maxLinkLength then
if pagePartial == link and (charAfter == '/' or charAfter == '') and linkLength > maxLinkLength then
maxLinkLength = linkLength
maxLinkLength = linkLength
Line 209: Line 937:
end)
end)


if this then tabArgs[this].this = true end
if not this then return end
tabArgs[this].this = true
end
end


function Tabs._buildContentDiv(hasContent, hybridTabs, noPadding)
function Tabs._buildContentDiv(hasContent, hybridTabs, noPadding)
if hasContent then
if hasContent then
local contentDiv = mw.html.create('div'):addClass('tabs-content')
local contentDiv = mw.html.create('div')
:addClass('tabs-content')
if hybridTabs then
if hybridTabs then
contentDiv:css('border-style', 'none !important'):css('padding', '0 !important')
contentDiv
:css('border-style', 'none !important')
:css('padding', '0 !important')
elseif noPadding then
elseif noPadding then
contentDiv:css('padding', '0 !important')
contentDiv
:css('padding', '0 !important')
end
end
return contentDiv
return contentDiv
Line 224: Line 957:


local style = ''
local style = ''
if hybridTabs then style = 'border-style:none !important; padding:0 !important;'
if hybridTabs then
elseif noPadding then style = 'padding:0 !important;' end
style = 'border-style:none !important; padding:0 !important;'
return '\n<div class="tabs-content" style="' .. style .. '">' end
elseif noPadding then
style = 'padding:0 !important;'
end
return '\n<div class="tabs-content" style="' .. style .. '">'
end


function Tabs._single(tab, showHeader)
function Tabs._single(tab, showHeader)
local header
local header
if showHeader then
if showHeader then
header = mw.html.create():tag('h6'):wikitext(tab.name):done():newline()
header = mw.html.create()
:tag('h6'):wikitext(tab.name):done()
:newline()
end
end
return mw.html.create():node(header):node(tab.content)
return mw.html.create()
:node(header)
:node(tab.content)
end
end


function Tabs._getDisplayNameFromLink(link)
function Tabs._getDisplayNameFromLink(link)
local parts = mw.text.split(link, '/', true)
local linkParts = mw.text.split(link, '/', true)
return parts[#parts]
return linkParts[#linkParts]
end
 
function Tabs.static(args)
args = args or {}
 
local tabArgs = Tabs._readArguments(args, {allowThis2 = true})
local tabCount = #tabArgs
if tabCount == 0 then return end
 
Tabs._setThis(tabArgs)
 
local tabs = mw.html.create('ul')
:attr('class', 'nav nav-tabs navigation-not-searchable tabs tabs' .. tabCount)
:attr('data-nosnippet')
 
local subTabs = mw.html.create()
 
Array.forEach(tabArgs, function(tab)
local name = tab.name or Tabs._getDisplayNameFromLink(tab.link)
local text = tab.link and Page.makeInternalLink({}, name, tab.link) or tab.name
tabs:tag('li'):addClass(tab.this and 'active' or nil):wikitext(text)
subTabs:node(tab.this and tab.tabs or nil)
end)
 
return mw.html.create()
:tag('div')
:addClass('tabs-static')
:attr('data-nosnippet', '')
:node(tabs)
:done()
:node(subTabs)
end
 
function Tabs.dynamic(args)
args = args or {}
 
local tabArgs = Tabs._readArguments(args, {removeEmptyTabs = Logic.readBool(args.removeEmptyTabs)})
local tabCount = #tabArgs
if tabCount == 0 then return end
 
local hasContent = Array.all(tabArgs, function(tab)
return Logic.isNotEmpty(tab.content) end)
local allEmpty = Array.all(tabArgs, function(tab)
return Logic.isEmpty(tab.content) end)
assert(hasContent or allEmpty, 'Some of the tabs have contents while others do not')
 
local isSingular = tabCount == 1 and hasContent
if isSingular and not Logic.readBool(args.showSingularAsTab) then
return Tabs._single(tabArgs[1], not Logic.readBool(args.suppressHeader))
end
 
local tabs = mw.html.create('ul')
:addClass('nav nav-tabs tabs tabs' .. tabCount)
 
if not Array.any(tabArgs, Operator.property('this')) then
tabArgs[1].this = true
end
 
local build = function(obj, elementType, content, class, isActive)
local element = mw.html.create(elementType)
:addClass(class)
:addClass(isActive and 'active' or nil)
:newline()
:node(content)
 
obj:newline():node(element)
end
 
Array.forEach(tabArgs, function(tabData, tabIndex)
build(tabs, 'li', tabData.name, 'tab' .. tabIndex, tabData.this)
end)
 
if not Logic.nilOr(Logic.readBoolOrNil(args['hide-showall']), isSingular) then
tabs:tag('li')
:addClass('show-all')
:wikitext('Show All')
end
 
tabs:newline()
 
local contents = Tabs._buildContentDiv(
hasContent,
Logic.readBool(args['hybrid-tabs']),
Logic.readBool(args['no-padding'])
)
 
if not hasContent then
return '<div class="tabs-dynamic navigation-not-searchable" data-nosnippet>\n'
.. tostring(tabs) .. contents
end
 
Array.forEach(tabArgs, function(tabData, tabIndex)
build(contents, 'div', tabData.content, 'content' .. tabIndex, tabData.this)
end)
 
return mw.html.create('div')
:addClass('tabs-dynamic navigation-not-searchable')
:attr('data-nosnippet')
:node(tabs)
:newline()
:node(contents)
end
end


return {
return Tabs
  static = Tabs.static,
  dynamic = Tabs.dynamic,
}