Jump to content

Module:Feedback

From The Petit Planet Wiki
Revision as of 03:35, 16 September 2026 by ReisuDesign (talk | contribs) (Created page with "local p = {} ------------------------------------------------------------ -- Helpers ------------------------------------------------------------ local function isTruthy(v) if v == nil then return false end v = mw.text.trim(tostring(v)):lower() return v ~= '' and v ~= '0' and v ~= 'false' and v ~= 'no' and v ~= 'off' end local function formatUser(user) user = mw.text.trim(tostring(user or '')) if user...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Module documentation

This module implements {{Feedback}}.

local p = {}

------------------------------------------------------------
-- Helpers
------------------------------------------------------------

local function isTruthy(v)
    if v == nil then
        return false
    end

    v = mw.text.trim(tostring(v)):lower()

    return v ~= ''
        and v ~= '0'
        and v ~= 'false'
        and v ~= 'no'
        and v ~= 'off'
end


local function formatUser(user)
    user = mw.text.trim(tostring(user or ''))

    if user == '' or user:lower() == 'anonymous' then
        return 'Anonymous'
    end

    return string.format(
        '[[User:%s|%s]]',
        user,
        user
    )
end


------------------------------------------------------------
-- Feedback card
------------------------------------------------------------

local function renderCard(entry, showInstruction)

    local resolved = (entry.status == 'resolved')

    local user = entry.user or 'Anonymous'
    local feedback = entry.feedback or 'No feedback provided'
    local page = entry.page

    local root = mw.html.create('div')
        :addClass('feedback-box')
        :addClass(
            resolved
                and 'feedback-box--resolved'
                or 'feedback-box--unresolved'
        )


    --------------------------------------------------------
    -- Metadata
    --------------------------------------------------------

    local meta = root:tag('div')
        :addClass('feedback-meta')


    if page and page ~= '' then

        meta:tag('span')
            :addClass('feedback-page')
            :wikitext(
                string.format('[[%s]]', page)
            )

        meta:tag('span')
            :addClass('feedback-separator')
            :wikitext(' • ')

    end


    meta:tag('span')
        :addClass('feedback-user')
        :wikitext(formatUser(user))


    meta:tag('span')
        :addClass('feedback-separator')
        :wikitext(' • ')


    meta:tag('span')
        :addClass('feedback-status')
        :addClass(
            resolved
                and 'feedback-status--resolved'
                or 'feedback-status--unresolved'
        )
        :wikitext(
            resolved
                and 'Resolved'
                or 'Unresolved'
        )


    --------------------------------------------------------
    -- Feedback text
    --------------------------------------------------------

    root:tag('div')
        :addClass('feedback-body')
        :wikitext(feedback)


    --------------------------------------------------------
    -- Resolution instruction
    --------------------------------------------------------

    if showInstruction and not resolved then

        root:tag('div')
            :addClass('feedback-instruction')
            :wikitext(
                'If this feedback has been resolved, edit this page '
                .. 'and set <code>|resolved=true</code>.'
            )

    end


    return tostring(root)
end


------------------------------------------------------------
-- Save feedback
------------------------------------------------------------

local function saveFeedback(data)

    if not (mw.ext and mw.ext.bucket) then
        return false
    end

    local bucket = mw.ext.bucket('feedback')

    if not bucket then
        return false
    end


    local success = pcall(function()

        bucket.put({
            page = data.page,
            user = data.user,
            status = data.status,
            feedback = data.feedback
        })

    end)


    return success
end


------------------------------------------------------------
-- Main
--
-- Used by Template:Feedback
--
-- Example:
--
-- {{Feedback
--  |feedback=Something could be improved here.
--  |user=Rei
--  |resolved=false
-- }}
------------------------------------------------------------

function p.main(frame)

    local parent = frame:getParent()

    local args =
        parent and parent.args
        or frame.args


    --------------------------------------------------------
    -- Get page title
    --------------------------------------------------------

    local pageTitle = nil

    if parent and parent.getTitle then

        local title = parent:getTitle()

        if title
            and title ~= ''
            and not title:find('^Template:')
        then
            pageTitle = title
        end

    end


    return p._main(args, pageTitle)

end


function p._main(args, pageTitle)

    local feedback =
        args.feedback
        or 'No feedback provided'


    local user =
        args.user
        or 'Anonymous'


    local resolved =
        isTruthy(args.resolved)


    local status =
        resolved
            and 'resolved'
            or 'unresolved'


    local currentPage =
        pageTitle
        or mw.title.getCurrentTitle().fullText


    --------------------------------------------------------
    -- Save to Bucket
    --------------------------------------------------------

    saveFeedback({
        page = currentPage,
        user = user,
        status = status,
        feedback = feedback
    })


    --------------------------------------------------------
    -- Render the feedback card
    --------------------------------------------------------

    local card = renderCard({

        page = nil,

        user = user,

        status = status,

        feedback = feedback

    }, true)


    --------------------------------------------------------
    -- Categorize feedback
    --------------------------------------------------------

    local category

    if resolved then

        category =
            '[[Category:Resolved Feedback]]'

    else

        category =
            '[[Category:Unresolved Feedback]]'

    end


    return card .. category

end


------------------------------------------------------------
-- Render feedback list
------------------------------------------------------------

local function renderFeedbackList(results)

    if not results or #results == 0 then
        return '<em>No feedback entries found.</em>'
    end


    local list = {}


    for _, row in ipairs(results) do

        table.insert(
            list,
            renderCard(row, false)
        )

    end


    return table.concat(list, '\n')

end


------------------------------------------------------------
-- View feedback
--
-- Examples:
--
-- {{#invoke:Feedback|view}}
--
-- {{#invoke:Feedback|view|status=all}}
--
-- {{#invoke:Feedback|view|status=resolved}}
--
-- {{#invoke:Feedback|view|status=unresolved}}
------------------------------------------------------------

function p.view(frame)

    local parent = frame:getParent()


    local args

    if frame
        and frame.args
        and frame.args.status
    then

        args = frame.args

    elseif parent
        and parent.args
        and parent.args.status
    then

        args = parent.args

    elseif frame
        and frame.args
    then

        args = frame.args

    elseif parent
        and parent.args
    then

        args = parent.args

    else

        args = {}

    end


    return p._view(args, frame)

end


function p._view(args, frame)

    --------------------------------------------------------
    -- Check Bucket
    --------------------------------------------------------

    if not (mw.ext and mw.ext.bucket) then

        return
            '<em>Bucket extension is not available.</em>'

    end


    frame = frame or mw.getCurrentFrame()


    --------------------------------------------------------
    -- Determine requested status
    --------------------------------------------------------

    local rawStatus =
        args.status
        or args[1]


    local filterStatus =
        rawStatus
        and mw.text.trim(rawStatus):lower()
        or nil


    --------------------------------------------------------
    -- Specific status
    --------------------------------------------------------

    if filterStatus == 'resolved'
        or filterStatus == 'unresolved'
    then

        local query =
            mw.ext.bucket('feedback')


        query.select(
            'page',
            'user',
            'status',
            'feedback'
        )


        query.where(
            'status',
            filterStatus
        )


        return renderFeedbackList(
            query.run()
        )

    end


    --------------------------------------------------------
    -- All feedback
    --------------------------------------------------------

    if filterStatus == 'all' then

        local query =
            mw.ext.bucket('feedback')


        query.select(
            'page',
            'user',
            'status',
            'feedback'
        )


        return renderFeedbackList(
            query.run()
        )

    end


    --------------------------------------------------------
    -- Tabber mode
    --
    -- Default:
    --
    -- All
    -- Unresolved
    -- Resolved
    --------------------------------------------------------

    local qAll =
        mw.ext.bucket('feedback')


    qAll.select(
        'page',
        'user',
        'status',
        'feedback'
    )


    local allEntries =
        qAll.run()


    local allList = {}
    local unresolvedList = {}
    local resolvedList = {}


    --------------------------------------------------------
    -- Split entries by status
    --------------------------------------------------------

    if allEntries and #allEntries > 0 then

        for _, row in ipairs(allEntries) do

            local card =
                renderCard(row, false)


            table.insert(
                allList,
                card
            )


            if row.status == 'resolved' then

                table.insert(
                    resolvedList,
                    card
                )

            else

                table.insert(
                    unresolvedList,
                    card
                )

            end

        end

    end


    --------------------------------------------------------
    -- Build tab contents
    --------------------------------------------------------

    local allContent

    if #allList > 0 then

        allContent =
            table.concat(allList, '\n')

    else

        allContent =
            '<em>No feedback entries found.</em>'

    end


    local unresolvedContent

    if #unresolvedList > 0 then

        unresolvedContent =
            table.concat(unresolvedList, '\n')

    else

        unresolvedContent =
            '<em>No unresolved feedback entries found.</em>'

    end


    local resolvedContent

    if #resolvedList > 0 then

        resolvedContent =
            table.concat(resolvedList, '\n')

    else

        resolvedContent =
            '<em>No resolved feedback entries found.</em>'

    end


    --------------------------------------------------------
    -- Tabber
    --------------------------------------------------------

    local tabberBody = string.format(
        'All=\n%s\n|-|Unresolved=\n%s\n|-|Resolved=\n%s',
        allContent,
        unresolvedContent,
        resolvedContent
    )


    return frame:callParserFunction(
        '#tag',
        {
            'tabber',
            tabberBody
        }
    )

end


return p