Module:Sidebar

From Remilia Wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:Sidebar/doc

--------------------------------------------------------------------------------
-- Module:Sidebar
-- Sidebar system for supplementary content and navigation.
--
-- INSTALLATION:
-- 1. Create page: Module:Sidebar
-- 2. Paste this entire file
-- 3. Create TemplateStyles page: Module:Sidebar/styles.css
--
-- USAGE:
-- {{#invoke:Sidebar|sidebar|name=Title|content=Content here}}
-- {{#invoke:Sidebar|portal|Portal Name}}
-- {{#invoke:Sidebar|collapsible|header=Title|content=...}}
--
-- @author Remilia Wiki
-- @license MIT
--------------------------------------------------------------------------------

local p = {}

--------------------------------------------------------------------------------
-- CONFIGURATION
--------------------------------------------------------------------------------

local MAX_SECTIONS = 20

--------------------------------------------------------------------------------
-- UTILITY FUNCTIONS
--------------------------------------------------------------------------------

local function trim(s)
    if not s then return nil end
    return tostring(s):match("^%s*(.-)%s*$")
end

local function isEmpty(s)
    if s == nil then return true end
    if type(s) == 'string' then
        return trim(s) == ''
    end
    return false
end

local function hasValue(s)
    return not isEmpty(s)
end

local function getArg(args, name, default)
    local val = args[name]
    if isEmpty(val) then return default end
    return trim(val)
end

local function getFrameArgs(frame)
    local args = {}
    local parentArgs = frame:getParent() and frame:getParent().args or {}
    local directArgs = frame.args or {}
    for k, v in pairs(directArgs) do args[k] = v end
    for k, v in pairs(parentArgs) do args[k] = v end
    return args
end

--------------------------------------------------------------------------------
-- BASIC SIDEBAR
--------------------------------------------------------------------------------

function p.sidebar(frame)
    local args = getFrameArgs(frame)

    local name = getArg(args, 'name') or getArg(args, 'title')
    local image = getArg(args, 'image')
    local imageSize = getArg(args, 'image_size', '200px')
    local caption = getArg(args, 'caption')
    local content = getArg(args, 'content') or getArg(args, 1)
    local below = getArg(args, 'below')
    local above = getArg(args, 'above')
    local bodyclass = getArg(args, 'bodyclass')
    local position = getArg(args, 'position', 'right')

    -- Build the sidebar
    local container = mw.html.create('div')
        :addClass('sidebar')

    if position == 'left' then
        container:addClass('sidebar-left')
    end

    if hasValue(bodyclass) then
        container:addClass(bodyclass)
    end

    -- Title
    if hasValue(name) then
        container:tag('div')
            :addClass('sidebar-title')
            :wikitext(name)
    end

    -- Above
    if hasValue(above) then
        container:tag('div')
            :addClass('sidebar-above')
            :wikitext(above)
    end

    -- Image
    if hasValue(image) then
        local imageDiv = container:tag('div')
            :addClass('sidebar-image')

        imageDiv:wikitext('[[File:' .. image .. '|' .. imageSize .. ']]')

        if hasValue(caption) then
            imageDiv:tag('div')
                :addClass('sidebar-caption')
                :wikitext(caption)
        end
    end

    -- Content
    if hasValue(content) then
        container:tag('div')
            :addClass('sidebar-content')
            :wikitext(content)
    end

    -- Numbered sections (heading1/content1, heading2/content2, etc.)
    for i = 1, MAX_SECTIONS do
        local heading = getArg(args, 'heading' .. i)
        local sectionContent = getArg(args, 'content' .. i)

        if hasValue(heading) or hasValue(sectionContent) then
            if hasValue(heading) then
                container:tag('div')
                    :addClass('sidebar-heading')
                    :wikitext(heading)
            end
            if hasValue(sectionContent) then
                container:tag('div')
                    :addClass('sidebar-content')
                    :wikitext(sectionContent)
            end
        end
    end

    -- Below
    if hasValue(below) then
        container:tag('div')
            :addClass('sidebar-below')
            :wikitext(below)
    end

    return tostring(container)
end

--------------------------------------------------------------------------------
-- COLLAPSIBLE SIDEBAR
--------------------------------------------------------------------------------

function p.collapsible(frame)
    local args = getFrameArgs(frame)

    local title = getArg(args, 'title') or getArg(args, 'name')
    local state = getArg(args, 'state', 'autocollapse')
    local bodyclass = getArg(args, 'bodyclass')
    local position = getArg(args, 'position', 'right')

    if isEmpty(title) then
        return '<span class="error">Error: Sidebar title required</span>'
    end

    -- Build the sidebar
    local container = mw.html.create('div')
        :addClass('sidebar')
        :addClass('sidebar-collapsible')
        :addClass('mw-collapsible')

    if state == 'collapsed' then
        container:addClass('mw-collapsed')
    end

    if position == 'left' then
        container:addClass('sidebar-left')
    end

    if hasValue(bodyclass) then
        container:addClass(bodyclass)
    end

    -- Title (clickable header)
    container:tag('div')
        :addClass('sidebar-title')
        :addClass('mw-collapsible-toggle')
        :wikitext(title)

    -- Content container
    local contentContainer = container:tag('div')
        :addClass('mw-collapsible-content')

    -- Numbered sections
    for i = 1, MAX_SECTIONS do
        local heading = getArg(args, 'heading' .. i)
        local sectionContent = getArg(args, 'content' .. i) or getArg(args, 'list' .. i)

        if hasValue(heading) or hasValue(sectionContent) then
            if hasValue(heading) then
                contentContainer:tag('div')
                    :addClass('sidebar-heading')
                    :wikitext(heading)
            end
            if hasValue(sectionContent) then
                contentContainer:tag('div')
                    :addClass('sidebar-content')
                    :wikitext(sectionContent)
            end
        end
    end

    -- General content
    local content = getArg(args, 'content')
    if hasValue(content) then
        contentContainer:tag('div')
            :addClass('sidebar-content')
            :wikitext(content)
    end

    -- Below
    local below = getArg(args, 'below')
    if hasValue(below) then
        contentContainer:tag('div')
            :addClass('sidebar-below')
            :wikitext(below)
    end

    return tostring(container)
end

--------------------------------------------------------------------------------
-- PORTAL SIDEBAR
--------------------------------------------------------------------------------

function p.portal(frame)
    local args = getFrameArgs(frame)

    local name = getArg(args, 1) or getArg(args, 'name')
    if isEmpty(name) then
        return '<span class="error">Error: Portal name required</span>'
    end

    local image = getArg(args, 'image')
    local imageSize = getArg(args, 'image_size', '32px')

    -- Build the portal box
    local container = mw.html.create('div')
        :addClass('sidebar')
        :addClass('sidebar-portal')

    local content = container:tag('div')
        :addClass('sidebar-content')
        :addClass('portal-content')

    -- Icon
    if hasValue(image) then
        content:wikitext('[[File:' .. image .. '|' .. imageSize .. '|link=Portal:' .. name .. ']] ')
    end

    -- Link
    content:wikitext('[[Portal:' .. name .. '|' .. name .. ' portal]]')

    return tostring(container)
end

--------------------------------------------------------------------------------
-- TOCLIMIT (Table of contents limiter)
--------------------------------------------------------------------------------

function p.toclimit(frame)
    local args = getFrameArgs(frame)

    local limit = getArg(args, 1) or getArg(args, 'limit', '2')

    -- Generate CSS to hide TOC levels
    local css = mw.html.create('style')
        :wikitext('.toclevel-' .. (tonumber(limit) + 1) .. ', ' ..
                  '.toclevel-' .. (tonumber(limit) + 2) .. ', ' ..
                  '.toclevel-' .. (tonumber(limit) + 3) .. ', ' ..
                  '.toclevel-' .. (tonumber(limit) + 4) .. ', ' ..
                  '.toclevel-' .. (tonumber(limit) + 5) .. ', ' ..
                  '.toclevel-' .. (tonumber(limit) + 6) .. ' { display: none; }')

    return tostring(css)
end

return p