Module:Quotation

From Remilia Wiki
Jump to navigation Jump to search

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

--------------------------------------------------------------------------------
-- Module:Quotation
-- Unified quotation system for blockquotes, pull quotes, and poetry.
--
-- INSTALLATION:
-- 1. Create page: Module:Quotation
-- 2. Paste this entire file
-- 3. Create TemplateStyles page: Module:Quotation/styles.css
--
-- USAGE:
-- {{#invoke:Quotation|blockquote|text=Quote here|author=Name}}
-- {{#invoke:Quotation|quotebox|quote=Quote here|author=Name|align=right}}
-- {{#invoke:Quotation|cquote|text=Quote here}}
-- {{#invoke:Quotation|poem|text=Poetry here}}
--
-- @author Remilia Wiki
-- @license MIT
--------------------------------------------------------------------------------

local p = {}

--------------------------------------------------------------------------------
-- 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

--- Build attribution line
local function buildAttribution(author, source, title)
    local parts = {}

    if hasValue(author) then
        table.insert(parts, author)
    end

    if hasValue(title) then
        table.insert(parts, "''" .. title .. "''")
    end

    if hasValue(source) then
        if hasValue(title) then
            table.insert(parts, "(" .. source .. ")")
        else
            table.insert(parts, "''" .. source .. "''")
        end
    end

    if #parts == 0 then
        return nil
    end

    return table.concat(parts, ', ')
end

--------------------------------------------------------------------------------
-- BLOCKQUOTE
--------------------------------------------------------------------------------

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

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

    local author = getArg(args, 'author')
    local source = getArg(args, 'source')
    local title = getArg(args, 'title')
    local character = getArg(args, 'character')

    -- Build the blockquote
    local bq = mw.html.create('blockquote')
        :addClass('quote-block')

    -- Quote text
    bq:tag('div')
        :addClass('quote-text')
        :wikitext(text)

    -- Attribution
    local attribution = buildAttribution(author, source, title)
    if hasValue(attribution) or hasValue(character) then
        local attrDiv = bq:tag('div')
            :addClass('quote-attribution')

        local attrText = ''
        if hasValue(character) then
            attrText = character
            if hasValue(attribution) then
                attrText = attrText .. ' in ' .. attribution
            end
        else
            attrText = attribution
        end

        attrDiv:wikitext('— ' .. attrText)
    end

    return tostring(bq)
end

--------------------------------------------------------------------------------
-- QUOTE BOX (Pull Quote)
--------------------------------------------------------------------------------

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

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

    local author = getArg(args, 'author')
    local source = getArg(args, 'source')
    local align = getArg(args, 'align', 'right')
    local width = getArg(args, 'width', '30%')

    -- Validate alignment
    if align ~= 'left' and align ~= 'right' and align ~= 'center' and align ~= 'none' then
        align = 'right'
    end

    -- Build the quote box
    local box = mw.html.create('div')
        :addClass('quote-box')
        :addClass('quote-box-' .. align)

    -- Apply width as inline style (CSS can't handle arbitrary values)
    if hasValue(width) then
        box:css('width', width)
    end

    -- Quote text
    box:tag('div')
        :addClass('quote-box-text')
        :wikitext(quote)

    -- Attribution
    local attribution = buildAttribution(author, source)
    if hasValue(attribution) then
        box:tag('div')
            :addClass('quote-box-attribution')
            :wikitext('— ' .. attribution)
    end

    return tostring(box)
end

--------------------------------------------------------------------------------
-- CQUOTE (Centered quote with decorative marks)
--------------------------------------------------------------------------------

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

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

    local author = getArg(args, 'author')
    local source = getArg(args, 'source')

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

    -- Opening quote mark
    container:tag('div')
        :addClass('quote-mark quote-mark-open')
        :wikitext('"')

    -- Quote text
    container:tag('div')
        :addClass('quote-centered-text')
        :wikitext(text)

    -- Closing quote mark
    container:tag('div')
        :addClass('quote-mark quote-mark-close')
        :wikitext('"')

    -- Attribution
    local attribution = buildAttribution(author, source)
    if hasValue(attribution) then
        container:tag('div')
            :addClass('quote-centered-attribution')
            :wikitext('— ' .. attribution)
    end

    return tostring(container)
end

--------------------------------------------------------------------------------
-- POEM QUOTE
--------------------------------------------------------------------------------

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

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

    local author = getArg(args, 'author')
    local source = getArg(args, 'source') or getArg(args, 'title')
    local align = getArg(args, 'align', 'left')

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

    -- Poem text (preserve line breaks)
    container:tag('div')
        :addClass('quote-poem-text')
        :newline()
        :wikitext('<poem>' .. text .. '</poem>')

    -- Attribution
    local attribution = buildAttribution(author, source)
    if hasValue(attribution) then
        container:tag('div')
            :addClass('quote-poem-attribution')
            :wikitext('— ' .. attribution)
    end

    return tostring(container)
end

--------------------------------------------------------------------------------
-- VERSE (Simple verse formatting, no box)
--------------------------------------------------------------------------------

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

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

    local container = mw.html.create('div')
        :addClass('quote-verse')

    container:wikitext('<poem>' .. text .. '</poem>')

    return tostring(container)
end

return p