Module:Feedback: Difference between revisions
Appearance
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..." |
ReisuDesign (talk | contribs) No edit summary |
||
| Line 6: | Line 6: | ||
local function isTruthy(v) | 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 | end | ||
local function formatUser(user) | 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 | end | ||
| Line 41: | Line 41: | ||
local function renderCard(entry, showInstruction) | 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 | end | ||
| Line 138: | Line 138: | ||
local function saveFeedback(data) | 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 | end | ||
| Line 181: | Line 187: | ||
function p.main(frame) | 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 | end | ||
| Line 215: | Line 221: | ||
function p._main(args, pageTitle) | 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 | end | ||
| Line 299: | Line 314: | ||
local function renderFeedbackList(results) | 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 | end | ||
| Line 338: | Line 353: | ||
function p.view(frame) | 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 | end | ||
| Line 383: | Line 398: | ||
function p._view(args, frame) | 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 | end | ||
Revision as of 04:53, 16 September 2026
| Main | Documentation | Usage |
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