Module:TabberUtils

From Little Tail Wiki
Revision as of 18:48, 8 February 2025 by Admin (talk | contribs) (Created page with "-- 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...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

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