Jump to content

Module:Feedback: Difference between revisions

From The Petit Planet Wiki
ReisuDesign (talk | contribs)
No edit summary
ReisuDesign (talk | contribs)
No edit summary
Line 32: Line 32:
user
user
)
)
end
local function escapeAttribute(value)
value = tostring(value or '')
return mw.text.nowiki(value)
end
end


Line 46: Line 53:
local feedback = entry.feedback or 'No feedback provided'
local feedback = entry.feedback or 'No feedback provided'
local page = entry.page
local page = entry.page
local id = entry.id
local resolvedBy = entry.resolvedby
local resolvedDate = entry.resolveddate


local root = mw.html.create('div')
local root = mw.html.create('div')
Line 113: Line 124:


--------------------------------------------------------
--------------------------------------------------------
-- Resolution instruction
-- Resolution information
--------------------------------------------------------
 
if resolved and resolvedBy and resolvedBy ~= '' then
 
local resolutionMeta =
'Resolved by ' .. formatUser(resolvedBy)
 
if resolvedDate and resolvedDate ~= '' then
resolutionMeta =
resolutionMeta
.. ' on '
.. resolvedDate
end
 
root:tag('div')
:addClass('feedback-resolution')
:wikitext(resolutionMeta)
 
end
 
 
--------------------------------------------------------
-- Resolution controls
--------------------------------------------------------
--------------------------------------------------------


if showInstruction and not resolved then
if showInstruction and not resolved then


root:tag('div')
local instruction = root:tag('div')
:addClass('feedback-instruction')
:addClass('feedback-instruction')
instruction:tag('span')
:wikitext(
:wikitext(
'If this feedback has been resolved, edit this page '
'If this feedback has been resolved, '
.. 'and set <code>|resolved=true</code>.'
.. 'click the button below.'
)
)
if id and id ~= '' then
instruction:tag('button')
:addClass('feedback-resolve-button')
:attr('type', 'button')
:attr('data-feedback-id', escapeAttribute(id))
:wikitext('Resolve')
end


end
end
Line 182: Line 230:
--  |user=Rei
--  |user=Rei
--  |resolved=false
--  |resolved=false
--  |id=1758012345678
--  |resolvedby=
--  |resolveddate=
-- }}
-- }}
------------------------------------------------------------
------------------------------------------------------------
Line 239: Line 290:
and 'resolved'
and 'resolved'
or 'unresolved'
or 'unresolved'
local id =
mw.text.trim(
tostring(args.id or '')
)
local resolvedBy =
mw.text.trim(
tostring(args.resolvedby or '')
)
local resolvedDate =
mw.text.trim(
tostring(args.resolveddate or '')
)




Line 279: Line 348:
status = status,
status = status,


feedback = feedback
feedback = feedback,
 
id = id,
 
resolvedby = resolvedBy,
 
resolveddate = resolvedDate


}, true)
}, true)
Line 339: Line 414:
------------------------------------------------------------
------------------------------------------------------------
-- View feedback
-- View feedback
--
-- Examples:
--
-- {{#invoke:Feedback|view}}
--
-- {{#invoke:Feedback|view|status=all}}
--
-- {{#invoke:Feedback|view|status=resolved}}
--
-- {{#invoke:Feedback|view|status=unresolved}}
------------------------------------------------------------
------------------------------------------------------------


Line 488: Line 553:
--------------------------------------------------------
--------------------------------------------------------
-- Tabber mode
-- Tabber mode
--
-- Default:
--
-- All
-- Unresolved
-- Resolved
--------------------------------------------------------
--------------------------------------------------------



Revision as of 05:09, 16 September 2026

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


local function escapeAttribute(value)
	value = tostring(value or '')

	return mw.text.nowiki(value)
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 id = entry.id
	local resolvedBy = entry.resolvedby
	local resolvedDate = entry.resolveddate


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

	if resolved and resolvedBy and resolvedBy ~= '' then

		local resolutionMeta =
			'Resolved by ' .. formatUser(resolvedBy)

		if resolvedDate and resolvedDate ~= '' then
			resolutionMeta =
				resolutionMeta
				.. ' on '
				.. resolvedDate
		end

		root:tag('div')
			:addClass('feedback-resolution')
			:wikitext(resolutionMeta)

	end


	--------------------------------------------------------
	-- Resolution controls
	--------------------------------------------------------

	if showInstruction and not resolved then

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


		instruction:tag('span')
			:wikitext(
				'If this feedback has been resolved, '
				.. 'click the button below.'
			)


		if id and id ~= '' then

			instruction:tag('button')
				:addClass('feedback-resolve-button')
				:attr('type', 'button')
				:attr('data-feedback-id', escapeAttribute(id))
				:wikitext('Resolve')

		end

	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
--  |id=1758012345678
--  |resolvedby=
--  |resolveddate=
-- }}
------------------------------------------------------------

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 id =
		mw.text.trim(
			tostring(args.id or '')
		)


	local resolvedBy =
		mw.text.trim(
			tostring(args.resolvedby or '')
		)


	local resolvedDate =
		mw.text.trim(
			tostring(args.resolveddate or '')
		)


	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,

		id = id,

		resolvedby = resolvedBy,

		resolveddate = resolvedDate

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

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

	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