Module:Sandbox/LVL: Difference between revisionsGive feedback
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
-- Single-file Tabs module for deadlock.wiki | |||
-- All dependencies inlined to avoid Lua.import | |||
-- Inlined Module:Array (partial stub - expand if needed) | |||
local Array = {} | |||
Array.forEach = function(elements, funct) | |||
for index, element in ipairs(elements) do | |||
funct(element, index) | |||
end | |||
end | |||
Array.any = function(tbl, predicate) | |||
for _, v in ipairs(tbl) do | |||
if predicate(v) then return true end | |||
end | |||
return false | |||
end | |||
Array.all = function(tbl, predicate) | |||
for _, v in ipairs(tbl) do | |||
if not predicate(v) then return false end | |||
end | |||
return true | |||
end | |||
-- Inlined Module:Class | |||
local Class = {} | |||
function Class.export(tbl, opts) | |||
local result = {} | |||
for _, name in ipairs(opts.exports) do | |||
result[name] = tbl[name] | |||
end | |||
return result | |||
end | |||
-- Inlined Module:Logic | |||
local Logic = {} | |||
function Logic.readBool(val) | |||
return val == true or val == 'true' or val == '1' | |||
end | |||
function Logic.readBoolOrNil(val) | |||
if val == nil then return nil end | |||
return Logic.readBool(val) | |||
end | |||
function Logic.isEmpty(val) | |||
return val == nil or val == '' | |||
end | |||
function Logic.isNotEmpty(val) | |||
return not Logic.isEmpty(val) | |||
end | |||
function Logic.nilOr(val, fallback) | |||
if val == nil then return fallback else return val end | |||
end | |||
-- Inlined Module:Operator | |||
local Operator = {} | |||
function Operator.property(prop) | |||
return function(tbl) | |||
return tbl and tbl[prop] | |||
end | |||
end | |||
-- Inlined Module:Page | |||
local Page = {} | |||
function Page.makeInternalLink(_, text, link) | |||
return string.format('[[%s|%s]]', link, text) | |||
end | |||
-- Inlined Module:Table | |||
local Table = {} | |||
function Table.extract(tbl, key) | |||
local val = tbl[key] | |||
if val ~= '' then return val end | |||
return nil | |||
end | |||
-- Begin Tabs Module | |||
local Tabs = {} | local Tabs = {} | ||
-- | function Tabs.static(args) | ||
local function readBool( | 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 | |||
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 = #link | |||
local charAfter = fullPageName:sub(linkLength + 1, linkLength + 1) | |||
local pagePartial = fullPageName:sub(1, linkLength) | |||
if pagePartial == link and (charAfter == '/' or charAfter == '') and linkLength > maxLinkLength then | |||
maxLinkLength = linkLength | |||
this = tabIndex | |||
end | |||
end) | |||
if this then tabArgs[this].this = true end | |||
end | end | ||
local function | 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 | end | ||
function Tabs._getDisplayNameFromLink(link) | |||
function Tabs. | local parts = mw.text.split(link, '/', true) | ||
return parts[#parts] | |||
end | end | ||
return Tabs | return { | ||
static = Tabs.static, | |||
dynamic = Tabs.dynamic, | |||
} | |||