Jump to content

Module:Feedback

From The Petit Planet Wiki
Revision as of 04:53, 16 September 2026 by ReisuDesign (talk | contribs)
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, 'Bucket extension is not available'
	end


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

	if not bucket then
		return false, 'Feedback bucket could not be opened'
	end


	local success, err = pcall(function()

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

	end)


	if not success then
		return false, tostring(err)
	end


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

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


	if not saved then

		return '<strong style="color:red;">Feedback bucket error: '
			.. mw.text.nowiki(saveError or 'Unknown error')
			.. '</strong>'

	end


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