Module:TabberUtils
From Little Tail Wiki
Documentation for this module may be created at Module:TabberUtils/doc
-- Adapted from ZeldaWiki: https://zeldawiki.wiki/w/index.php?title=Module:UtilsLayout/Tabs
-- Edited by Vish
local p = {}
local h = {}
-- Function to parse arguments
function p.parse(frameArgs)
local args = {
tabs = {},
align = "left",
default = 1,
columns = nil,
position = "top",
distribution = nil,
tabAlign = "center",
contentAlign = "left",
class = nil,
}
-- Parse named parameters
for k, v in pairs(frameArgs) do
if k == "align" then
args.align = v
elseif k == "default" then
args.default = tonumber(v) or 1
elseif k == "columns" then
args.columns = tonumber(v)
elseif k == "position" then
args.position = v
elseif k == "distribution" then
args.distribution = v
elseif k == "tabAlign" then
args.tabAlign = v
elseif k == "contentAlign" then
args.contentAlign = v
elseif k == "class" then
args.class = v
end
end
-- Parse tab names and contents
local i = 1
while frameArgs[i] or frameArgs["Tab" .. i] do
local key = frameArgs[i] or frameArgs["Tab" .. i]
local value = frameArgs[i + 1] or frameArgs["Content" .. i]
if key and key ~= "" then
table.insert(args.tabs, {
tab = key,
content = value
})
end
i = i + (frameArgs[i] and 2 or 1)
end
return args
end
-- Function to sanitize tab names by removing MediaWiki markup like [[File:...]]
local function sanitizeTabName(tabName)
-- Remove file syntax [[File:...]]
tabName = mw.ustring.gsub(tabName, "%[%[File:.-%]%]", "")
-- Trim whitespace
tabName = mw.text.trim(tabName)
return tabName
end
-- Escape special characters in the tab name for pattern matching
local function escapePattern(text)
local escapeChars = { "%", ".", "-", "^", "$", "(", ")", "[", "]", "*", "+", "?", "{" ,"}" }
for _, char in ipairs(escapeChars) do
text = text:gsub("%" .. char, "%%" .. char)
end
return text
end
-- Function to count how many times a section name appears on the page
function h.getSectionCount(tabName)
local title = mw.title.getCurrentTitle()
local content = title:getContent()
if not content then
return 0
end
-- Sanitize tab name to avoid issues with MediaWiki markup
tabName = sanitizeTabName(tabName)
-- Escape special characters for pattern matching
local pattern = "\n==%s*" .. escapePattern(tabName) .. "%s*=="
local count = 0
for _ in mw.ustring.gmatch(content, pattern) do
count = count + 1
end
return count
end
-- Function to generate a unique tab hash, avoiding duplicate section names
function h.generateUniqueTabHash(tabName)
-- Sanitize tab name
tabName = sanitizeTabName(tabName)
local sectionCount = h.getSectionCount(tabName)
-- Start checking from the next available number
local uniqueName = tabName
local attempt = sectionCount + 1
local title = mw.title.getCurrentTitle()
local content = title:getContent()
while content and mw.ustring.match(content, "\n==%s*" .. escapePattern(uniqueName) .. "%s*==") do
attempt = attempt + 1
uniqueName = tabName .. "_" .. attempt
end
return mw.uri.anchorEncode(uniqueName)
end
function p.tabs(data, options)
local options = options or {}
local tabOptions = options.tabOptions or {}
local labelOptions = options.labelOptions or {}
local contentOptions = options.contentOptions or {}
local defaultTab = options.default or 1
local align = options.align or "left"
local tabAlign = options.tabAlign or "center"
local contentAlign = options.contentAlign or "left"
if #data == 1 and tabOptions.collapse then
return data[1].content
end
local tabContainer = h.tabContainer(data, defaultTab, tabAlign, tabOptions, labelOptions)
local tabContents = h.tabContents(data, defaultTab, contentAlign, contentOptions)
local html = mw.html.create("div")
:addClass("jw-tabs")
:addClass(tabOptions.stretch and "jw-tabs--stretch" or nil)
:addClass(tabOptions.columns and "jw-tabs--columns" or nil)
:addClass(options.class)
if tabOptions.position == "bottom" then
html:node(tabContents)
:node(tabContainer)
else
html:node(tabContainer)
:node(tabContents)
end
return tostring(html)
end
function h.tabContainer(data, defaultTab, align, tabOptions, labelOptions)
local position = tabOptions.position or "top"
local stretch = tabOptions.stretch
local columns = tabOptions.columns
local labelAlignVertical = labelOptions.alignVertical or "center"
local tabContainer = mw.html.create("div")
:addClass("tabcontainer tabcontainer-" .. position)
:addClass("tabcontainer--align-x-" .. align)
:attr("role", "tablist")
for i, tabData in ipairs(data) do
local label = tabData.label
local sanitizedLabel = h.generateUniqueTabHash(label)
local tab = mw.html.create("span")
:addClass("tab")
:addClass("tab--label-align-y-" .. labelAlignVertical)
:attr("role", "tab")
:attr("tabindex", "0")
:attr("data-tab-hash", sanitizedLabel)
:wikitext(label)
if i == defaultTab then
tab:addClass("active")
end
if columns then
tab:css({
["max-width"] = "calc(100%/" .. columns .. " - 2*" .. (columns - 1) .. "px)" -- the subtraction is to account for tab margins
})
end
tabContainer:node(tab)
end
return tabContainer
end
function h.tabContents(data, defaultTab, align, contentOptions)
local alignVertical = contentOptions.alignVertical or "top"
local fixedWidth = contentOptions.fixedWidth
local fixedHeight = contentOptions.fixedHeight
local tabContents = mw.html.create("div")
:addClass("tabcontents")
:addClass("tabcontents--align-x-" .. align)
:addClass("tabcontents--align-y-" .. alignVertical)
if fixedWidth then
tabContents:addClass("tabcontents--fixed-width")
if type(fixedWidth) == "number" then
tabContents:css("width", fixedWidth .. "px")
end
end
if fixedHeight then
tabContents:addClass("tabcontents--fixed-height")
if type(fixedHeight) == "number" then
tabContents:css("height", fixedHeight .. "px")
end
end
for i, tabData in ipairs(data) do
local content = mw.html.create("div")
:addClass("jw-tabs-content")
:attr("role", "tabpanel")
:wikitext("\n", tabData.content or "")
if i == defaultTab then
content:addClass("jw-tabs-content--active")
end
tabContents:node(content)
end
return tabContents
end
return p