Module:Sandbox/LVL: Difference between revisionsGive feedback
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
-- | -- @Liquipedia | ||
-- | -- page=Module:TabsCombined | ||
-- Combined version of Module:Tabs with all dependencies | |||
local Tabs = {} | |||
--[[ | |||
================================================================= | |||
Start of dependency: Module:Table | |||
================================================================= | |||
]] | |||
local Table = {} | |||
function Table.size(tbl) | |||
local | local i = 0 | ||
for _ in pairs(tbl) do | |||
for | i = i + 1 | ||
end | end | ||
return i | |||
end | end | ||
for _, | function Table.includes(tbl, value, isPattern) | ||
if | 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 | ||
for _, | function Table.getKeyOfValue(tbl, value) | ||
if | 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 | ||
function Table.isNotEmpty(tbl) | |||
return not Table.isEmpty(tbl) | |||
function | end | ||
function Table.copy(tbl) | |||
local result = {} | local result = {} | ||
for | for key, entry in pairs(tbl) do | ||
result[ | result[key] = entry | ||
end | end | ||
return result | return result | ||
end | end | ||
-- | 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 | 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 | if Logic.readBool(val) then | ||
return Logic. | 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 | |||
return | 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 | |||
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 | |||
function Array.flatMap(elements, funct) | |||
return Array.flatten(Array.map(elements, funct)) | |||
end | end | ||
function Array.all(tbl, predicate) | |||
for _, element in ipairs(tbl) do | |||
function | if not predicate(element) then | ||
return false | |||
return | end | ||
end | end | ||
return true | |||
end | end | ||
function Array.any(tbl, predicate) | |||
for _, element in ipairs(tbl) do | |||
function | if predicate(element) then | ||
return | return true | ||
end | |||
end | |||
return false | |||
end | end | ||
function Array.find(tbl, predicate) | |||
for index, element in ipairs(tbl) do | |||
function | if predicate(element, index) then | ||
return element | |||
end | |||
end | |||
return nil | return nil | ||
end | end | ||
- | function Array.groupBy(tbl, funct) | ||
local | 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 | function Array.extractKeys(tbl, iterator, ...) | ||
iterator = iterator or pairs | |||
local array = {} | |||
for key, _ in iterator(tbl, ...) do | |||
table.insert(array, key) | |||
end | |||
return array | |||
end | |||
function Array.extractValues(tbl, iterator, ...) | |||
local | iterator = iterator or pairs | ||
local array = {} | |||
for _, item in iterator(tbl, ...) do | |||
table.insert(array, item) | |||
end | |||
return array | |||
end | |||
function Array.forEach(elements, funct) | |||
for index, element in ipairs(elements) do | |||
funct(element, index) | |||
end | |||
end | |||
function Array.reduce(array, operator, initialValue) | |||
local aggregate | |||
if initialValue ~= nil then | |||
aggregate = initialValue | |||
else | |||
end | aggregate = array[1] | ||
end | |||
for index = initialValue ~= nil and 1 or 2, #array do | |||
aggregate = operator(aggregate, array[index]) | |||
end | |||
return aggregate | |||
end | end | ||
function | function Array.maxBy(array, funct, compare) | ||
compare = compare or Array.lexicalCompareIfTable | |||
local | local max, maxScore | ||
for _, item in ipairs(array) do | |||
local score = funct(item) | |||
if max == nil or compare(maxScore, score) then | |||
max = item | |||
maxScore = score | |||
end | |||
end | |||
return max | |||
end | |||
function Array.max(array, compare) | |||
return Array.maxBy(array, function(x) return x end, compare) | |||
end | |||
local | function Array.minBy(array, funct, compare) | ||
compare = compare or Array.lexicalCompareIfTable | |||
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 | |||
function Array.indexOf(array, pred) | |||
for ix, elem in ipairs(array) do | |||
if pred(elem, ix) then | |||
return ix | |||
end | |||
end | end | ||
return 0 | |||
end | |||
local | function Array.unique(elements) | ||
local elementCache = {} | |||
local uniqueElements = {} | |||
for _, element in ipairs(elements) do | |||
if elementCache[element] == nil then | |||
table.insert(uniqueElements, 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. | function Array.interleave(elements, x) | ||
local size = #elements | |||
return Array.flatMap(elements, function(element, index) | |||
if index == size then | |||
return {element} | |||
end | |||
return {element, x} | |||
end) | end) | ||
end | |||
--[[ | |||
================================================================= | |||
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 = {} | |||
function Page.exists(link) | |||
local existingPage = mw.title.new(link) | |||
if existingPage == nil then | |||
return false | |||
end | |||
return existingPage.exists | |||
end | |||
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 | if (options or {}).onlyIfExists == true and (not Page.exists(customLink)) then | ||
return | return nil | ||
end | end | ||
return '[[' .. customLink .. '|' .. display .. ']]' | |||
end | |||
function Page.makeExternalLink(display, link) | |||
if Logic.isEmpty(display) or Logic.isEmpty(link) then | |||
return nil | |||
end | |||
return '[' .. link .. ' ' .. display .. ']' | |||
end | |||
return mw. | function Page.pageifyLink(link) | ||
if Logic.isEmpty(link) then | |||
return nil | |||
end | |||
return (mw.ext.TeamLiquidIntegration.resolve_redirect(link):gsub(' ', '_')) | |||
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 = | local linkLength = string.len(link) | ||
local charAfter = | local charAfter = string.sub(fullPageName, linkLength + 1, linkLength + 1) | ||
local pagePartial = | 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 | 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 | local linkParts = mw.text.split(link, '/', true) | ||
return | 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 | ||
Revision as of 19:25, 7 July 2025
Documentation for this module may be created at Module:Sandbox/LVL/doc
-- @Liquipedia
-- page=Module:TabsCombined
-- Combined version of Module:Tabs with all dependencies
local Tabs = {}
--[[
=================================================================
Start of dependency: Module:Table
=================================================================
]]
local Table = {}
function Table.size(tbl)
local i = 0
for _ in pairs(tbl) do
i = i + 1
end
return i
end
function Table.includes(tbl, value, isPattern)
for _, entry in pairs(tbl) do
if isPattern and string.find(entry, value)
or not isPattern and entry == value then
return true
end
end
return false
end
function Table.getKeyOfValue(tbl, value)
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
return true
end
function Table.isNotEmpty(tbl)
return not Table.isEmpty(tbl)
end
function Table.copy(tbl)
local result = {}
for key, entry in pairs(tbl) do
result[key] = entry
end
return result
end
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 = {}
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)
return val == 'true' or val == 't' or val == 'yes' or val == 'y' or val == true or val == '1' or val == 1
end
function Logic.readBoolOrNil(val)
if Logic.readBool(val) then
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
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
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
function Array.flatMap(elements, funct)
return Array.flatten(Array.map(elements, funct))
end
function Array.all(tbl, predicate)
for _, element in ipairs(tbl) do
if not predicate(element) then
return false
end
end
return true
end
function Array.any(tbl, predicate)
for _, element in ipairs(tbl) do
if predicate(element) then
return true
end
end
return false
end
function Array.find(tbl, predicate)
for index, element in ipairs(tbl) do
if predicate(element, index) then
return element
end
end
return nil
end
function Array.groupBy(tbl, funct)
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 Array.extractKeys(tbl, iterator, ...)
iterator = iterator or pairs
local array = {}
for key, _ in iterator(tbl, ...) do
table.insert(array, key)
end
return array
end
function Array.extractValues(tbl, iterator, ...)
iterator = iterator or pairs
local array = {}
for _, item in iterator(tbl, ...) do
table.insert(array, item)
end
return array
end
function Array.forEach(elements, funct)
for index, element in ipairs(elements) do
funct(element, index)
end
end
function Array.reduce(array, operator, initialValue)
local aggregate
if initialValue ~= nil then
aggregate = initialValue
else
aggregate = array[1]
end
for index = initialValue ~= nil and 1 or 2, #array do
aggregate = operator(aggregate, array[index])
end
return aggregate
end
function Array.maxBy(array, funct, compare)
compare = compare or Array.lexicalCompareIfTable
local max, maxScore
for _, item in ipairs(array) do
local score = funct(item)
if max == nil or compare(maxScore, score) then
max = item
maxScore = score
end
end
return max
end
function Array.max(array, compare)
return Array.maxBy(array, function(x) return x end, compare)
end
function Array.minBy(array, funct, compare)
compare = compare or Array.lexicalCompareIfTable
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
return min
end
function Array.min(array, compare)
return Array.minBy(array, function(x) return x end, compare)
end
function Array.indexOf(array, pred)
for ix, elem in ipairs(array) do
if pred(elem, ix) then
return ix
end
end
return 0
end
function Array.unique(elements)
local elementCache = {}
local uniqueElements = {}
for _, element in ipairs(elements) do
if elementCache[element] == nil then
table.insert(uniqueElements, element)
elementCache[element] = true
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
function Array.interleave(elements, x)
local size = #elements
return Array.flatMap(elements, function(element, index)
if index == size then
return {element}
end
return {element, x}
end)
end
--[[
=================================================================
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
--[[
=================================================================
Start of dependency: Module:Page
=================================================================
]]
local Page = {}
function Page.exists(link)
local existingPage = mw.title.new(link)
if existingPage == nil then
return false
end
return existingPage.exists
end
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 (options or {}).onlyIfExists == true and (not Page.exists(customLink)) then
return nil
end
return '[[' .. customLink .. '|' .. display .. ']]'
end
function Page.makeExternalLink(display, link)
if Logic.isEmpty(display) or Logic.isEmpty(link) then
return nil
end
return '[' .. link .. ' ' .. display .. ']'
end
function Page.pageifyLink(link)
if Logic.isEmpty(link) then
return nil
end
return (mw.ext.TeamLiquidIntegration.resolve_redirect(link):gsub(' ', '_'))
end
--[[
=================================================================
Start of original Module:Tabs code
=================================================================
]]
function Tabs._readArguments(args, options)
local tabArgs = {}
local tabIndex = 1
local this = tonumber(args.This)
local this2 = tonumber(args.This2)
while args['name' .. tabIndex] or args['link' .. tabIndex] do
if args['content' .. tabIndex] or not options.removeEmptyTabs then
table.insert(tabArgs, {
name = Table.extract(args, 'name' .. tabIndex),
link = Table.extract(args, 'link' .. tabIndex),
content = Table.extract(args, 'content' .. tabIndex),
tabs = Table.extract(args, 'tabs' .. tabIndex),
this = this == tabIndex or (options.allowThis2 and this2 == tabIndex),
})
end
tabIndex = tabIndex + 1
end
if Logic.readBool(args.returnIfEmpty) then
return tabArgs
end
assert(Logic.isNotEmpty(tabArgs), 'You are trying to add a "Tabs" template without arguments for names nor links')
return tabArgs
end
function Tabs._setThis(tabArgs)
if Array.any(tabArgs, Operator.property('this')) then return end
local fullPageName = mw.title.getCurrentTitle().prefixedText
local this
local maxLinkLength = -1
Array.forEach(tabArgs, function (tab, tabIndex)
local link = tab.link
if not link then return end
link = link:gsub('_', ' ')
local linkLength = string.len(link)
local charAfter = string.sub(fullPageName, linkLength + 1, linkLength + 1)
local pagePartial = string.sub(fullPageName, 1, linkLength)
if pagePartial == link and (charAfter == '/' or charAfter == '') and linkLength > maxLinkLength then
maxLinkLength = linkLength
this = tabIndex
end
end)
if not this then return end
tabArgs[this].this = true
end
function Tabs._buildContentDiv(hasContent, hybridTabs, noPadding)
if hasContent then
local contentDiv = mw.html.create('div')
:addClass('tabs-content')
if hybridTabs then
contentDiv
:css('border-style', 'none !important')
:css('padding', '0 !important')
elseif noPadding then
contentDiv
:css('padding', '0 !important')
end
return contentDiv
end
local style = ''
if hybridTabs then
style = 'border-style:none !important; padding:0 !important;'
elseif noPadding then
style = 'padding:0 !important;'
end
return '\n<div class="tabs-content" style="' .. style .. '">'
end
function Tabs._single(tab, showHeader)
local header
if showHeader then
header = mw.html.create()
:tag('h6'):wikitext(tab.name):done()
:newline()
end
return mw.html.create()
:node(header)
:node(tab.content)
end
function Tabs._getDisplayNameFromLink(link)
local linkParts = mw.text.split(link, '/', true)
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
return Tabs