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 41: Line 41:
local function renderCard(entry, showInstruction)
local function renderCard(entry, showInstruction)


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


local user =
local user =
Line 69: Line 70:
)
)


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


--------------------------------------------------------
--------------------------------------------------------
Line 81: Line 83:
--------------------------------------------------------
--------------------------------------------------------


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


if page and page ~= '' then
if page and page ~= '' then
Line 98: Line 101:
:addClass('feedback-separator')
:addClass('feedback-separator')
:wikitext(' • ')
:wikitext(' • ')
end
end


Line 124: Line 128:


--------------------------------------------------------
--------------------------------------------------------
-- Feedback
-- Feedback body
--------------------------------------------------------
--------------------------------------------------------


Line 170: Line 174:
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]'
)
)
instruction:tag('span')
:addClass('feedback-resolve-button')
:attr('role', 'button')
:attr('tabindex', '0')
:attr('data-feedback-id', id)
:wikitext('Resolve')


else
else
Line 205: Line 213:
end
end


local success, err = pcall(function()
local success, err =
pcall(function()


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


end)
end)


if not success then
if not success then

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

	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. '
			)

			instruction:tag('span')
				:addClass('feedback-resolve-button')
				:attr('role', 'button')
				:attr('tabindex', '0')
				:attr('data-feedback-id', id)
				:wikitext('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