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 147: Line 147:


if resolvedDate ~= '' then
if resolvedDate ~= '' then
resolution:wikitext(
resolution:wikitext(
' on '
' on '
.. mw.text.nowiki(resolvedDate)
.. mw.text.nowiki(resolvedDate)
)
)
end
end
end
end
Line 168: Line 170:
instruction:wikitext(
instruction:wikitext(
'If this feedback has been resolved, click the button below. '
'If this feedback has been resolved, click the button below. '
.. '[#feedback-resolve-'
.. id
.. ' Resolve]'
)
)
-- Use an <a> instead of <button>.
-- MediaWiki sanitizes <button> when returned from wikitext.
instruction:tag('a')
:addClass('feedback-resolve-button')
:attr('href', '#')
:attr('role', 'button')
:attr('data-feedback-id', id)
:wikitext('Resolve')


else
else
Line 255: Line 251:
and not title:find('^Template:')
and not title:find('^Template:')
then
then
pageTitle = title
pageTitle = title
end
end
end
end
Line 316: Line 314:


if not saved then
if not saved then
return '<strong style="color:red;">Feedback bucket error: '
return '<strong style="color:red;">Feedback bucket error: '
.. mw.text.nowiki(
.. mw.text.nowiki(
Line 321: Line 320:
)
)
.. '</strong>'
.. '</strong>'
end
end



Revision as of 05:32, 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


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

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

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

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

	--------------------------------------------------------
	-- Meta
	--------------------------------------------------------

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

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

	--------------------------------------------------------
	-- Resolution information
	--------------------------------------------------------

	if resolved and resolvedBy ~= '' then

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

		resolution:wikitext(
			'Resolved by '
			.. formatUser(resolvedBy)
		)

		if resolvedDate ~= '' then

			resolution:wikitext(
				' on '
				.. mw.text.nowiki(resolvedDate)
			)

		end
	end

	--------------------------------------------------------
	-- Resolve instruction
	--------------------------------------------------------

	if showInstruction and not resolved then

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

		if id ~= '' then

			instruction:wikitext(
				'If this feedback has been resolved, click the button below. '
				.. '[#feedback-resolve-'
				.. id
				.. ' Resolve]'
			)

		else

			instruction:wikitext(
				'Add an ID to this feedback to enable the Resolve button.'
			)

		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,
			id = data.id,
			resolvedby = data.resolvedby,
			resolveddate = data.resolveddate
		})

	end)

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

	return true
end


------------------------------------------------------------
-- Main
------------------------------------------------------------

function p.main(frame)

	local parent =
		frame:getParent()

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

	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 id =
		args.id
		or ''

	local resolvedBy =
		args.resolvedby
		or ''

	local resolvedDate =
		args.resolveddate
		or ''

	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,
			id = id,
			resolvedby = resolvedBy,
			resolveddate = resolvedDate
		})

	if not saved then

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

	end

	--------------------------------------------------------
	-- Render card
	--------------------------------------------------------

	local card =
		renderCard({
			page = nil,
			user = user,
			status = status,
			feedback = feedback,
			id = id,
			resolvedby = resolvedBy,
			resolveddate = resolvedDate
		}, true)

	--------------------------------------------------------
	-- Categories
	--------------------------------------------------------

	local categories = {}

	if resolved then

		table.insert(
			categories,
			'[[Category:Resolved Feedback]]'
		)

	else

		table.insert(
			categories,
			'[[Category:Unresolved Feedback]]'
		)

	end

	if mw.text.trim(
		tostring(id)
	) == '' then

		table.insert(
			categories,
			'[[Category:Feedbacks Missing ID]]'
		)

	end

	return card
		.. table.concat(categories)
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)

	if not (mw.ext and mw.ext.bucket) then
		return '<em>Bucket extension is not available.</em>'
	end

	frame =
		frame
		or mw.getCurrentFrame()

	local rawStatus =
		args.status
		or args[1]

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

	--------------------------------------------------------
	-- Filtered views
	--------------------------------------------------------

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

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

		query.select(
			'page',
			'user',
			'status',
			'feedback',
			'id',
			'resolvedby',
			'resolveddate'
		)

		query.where(
			'status',
			filterStatus
		)

		return renderFeedbackList(
			query.run()
		)
	end

	--------------------------------------------------------
	-- All
	--------------------------------------------------------

	if filterStatus == 'all' then

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

		query.select(
			'page',
			'user',
			'status',
			'feedback',
			'id',
			'resolvedby',
			'resolveddate'
		)

		return renderFeedbackList(
			query.run()
		)
	end

	--------------------------------------------------------
	-- Default tabbed view
	--------------------------------------------------------

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

	qAll.select(
		'page',
		'user',
		'status',
		'feedback',
		'id',
		'resolvedby',
		'resolveddate'
	)

	local allEntries =
		qAll.run()

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

	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

	--------------------------------------------------------
	-- All content
	--------------------------------------------------------

	local allContent

	if #allList > 0 then

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

	else

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

	end

	--------------------------------------------------------
	-- Unresolved content
	--------------------------------------------------------

	local unresolvedContent

	if #unresolvedList > 0 then

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

	else

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

	end

	--------------------------------------------------------
	-- Resolved content
	--------------------------------------------------------

	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