Editing Module:Feedback
Appearance
The edit can be undone.
Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
| Latest revision | Your text | ||
| Line 2: | Line 2: | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
-- | -- Helpers | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
local function isTruthy( | local function isTruthy(v) | ||
if | if v == nil then | ||
return false | return false | ||
end | end | ||
v = mw.text.trim(tostring(v)):lower() | |||
return | 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 = trim(user) | user = mw.text.trim(tostring(user or '')) | ||
if user == '' then | if user == '' or user:lower() == 'anonymous' then | ||
return 'Anonymous' | return 'Anonymous' | ||
end | end | ||
| Line 43: | Line 35: | ||
local function | local function escapeAttribute(value) | ||
value = tostring(value or '') | |||
return mw.text.nowiki(value) | |||
end | end | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
-- | -- Feedback card | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
local function | local function renderCard(entry, showInstruction) | ||
local | 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) | |||
:addClass('feedback- | |||
) | ) | ||
meta:tag('span') | |||
:addClass('feedback-separator') | |||
:wikitext(' • ') | |||
: | |||
:wikitext(' | |||
end | end | ||
meta:tag('span') | |||
:addClass('feedback-user') | |||
:wikitext(formatUser(user)) | |||
meta:tag('span') | |||
:addClass('feedback-separator') | |||
:wikitext(' • ') | |||
meta:tag('span') | |||
:addClass('feedback-status') | :addClass('feedback-status') | ||
:addClass( | :addClass( | ||
| Line 329: | Line 112: | ||
or 'Unresolved' | or 'Unresolved' | ||
) | ) | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
| Line 334: | Line 118: | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
root:tag('div') | |||
:addClass('feedback- | :addClass('feedback-body') | ||
:wikitext( | :wikitext(feedback) | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
-- | -- Resolution information | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
local | if resolved and resolvedBy and resolvedBy ~= '' then | ||
:addClass('feedback- | 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 | end | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
-- | -- Resolution controls | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
if showInstruction and not resolved then | |||
local instruction = root:tag('div') | |||
:addClass('feedback-instruction') | |||
instruction:tag('span') | |||
:wikitext( | :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 | end | ||
return tostring(root) | return tostring(root) | ||
| Line 413: | Line 181: | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
-- | -- Save feedback | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
local function | 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') | |||
local bucket = | |||
if not bucket then | if not bucket then | ||
return false, ' | return false, 'Feedback bucket could not be opened' | ||
end | end | ||
local success, err = pcall(function() | local success, err = pcall(function() | ||
bucket.put({ | bucket.put({ | ||
page = data.page, | page = data.page, | ||
| Line 439: | Line 206: | ||
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 | ||
return false, tostring(err) | return false, tostring(err) | ||
end | end | ||
return true | return true | ||
| Line 456: | Line 223: | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
-- | -- Main | ||
-- | -- | ||
-- Used by Template:Feedback | -- Used by Template:Feedback | ||
-- | |||
-- Example: | |||
-- | |||
-- {{Feedback | |||
-- |feedback=Something could be improved here. | |||
-- |user=Rei | |||
-- |resolved=false | |||
-- |id=1758012345678 | |||
-- |resolvedby= | |||
-- |resolveddate= | |||
-- }} | |||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
function p.main(frame) | function p.main(frame) | ||
local parent = frame:getParent() | local parent = frame:getParent() | ||
| Line 467: | Line 246: | ||
parent and parent.args | parent and parent.args | ||
or frame.args | or frame.args | ||
-------------------------------------------------------- | |||
-- Get page title | |||
-------------------------------------------------------- | |||
local pageTitle = nil | local pageTitle = nil | ||
if parent and parent.getTitle then | if parent and parent.getTitle then | ||
local title = parent:getTitle() | local title = parent:getTitle() | ||
| Line 479: | Line 264: | ||
pageTitle = title | pageTitle = title | ||
end | end | ||
end | end | ||
return p._main( | |||
return p._main(args, pageTitle) | |||
end | end | ||
function p._main(args, pageTitle) | function p._main(args, pageTitle) | ||
local feedback = | local feedback = | ||
args.feedback | |||
or 'No feedback provided' | |||
local user = | local user = | ||
args.user | |||
or 'Anonymous' | |||
local resolved = | local resolved = | ||
isTruthy(args.resolved) | isTruthy(args.resolved) | ||
local status = | local status = | ||
| Line 511: | Line 294: | ||
or 'unresolved' | or 'unresolved' | ||
local id = | |||
mw.text.trim( | |||
tostring(args.id or '') | |||
) | |||
local | local resolvedBy = | ||
trim(args. | mw.text.trim( | ||
tostring(args.resolvedby or '') | |||
) | |||
local | local resolvedDate = | ||
trim(args. | mw.text.trim( | ||
tostring(args.resolveddate or '') | |||
) | |||
local | local currentPage = | ||
pageTitle | |||
or mw.title.getCurrentTitle().fullText | |||
-------------------------------------------------------- | -------------------------------------------------------- | ||
| Line 541: | Line 322: | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
local saved, saveError = | local saved, saveError = saveFeedback({ | ||
page = currentPage, | |||
user = user, | |||
status = status, | |||
feedback = feedback | |||
}) | |||
if not saved then | if not saved then | ||
return | |||
return '<strong style="color:red;">Feedback bucket error: ' | |||
.. mw.text.nowiki( | .. mw.text.nowiki(saveError or 'Unknown error') | ||
.. '</strong>' | .. '</strong>' | ||
end | end | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
-- Render | -- Render feedback card | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
local card = | local card = renderCard({ | ||
page = nil, | |||
user = user, | |||
status = status, | |||
feedback = feedback, | |||
id = id, | |||
resolvedby = resolvedBy, | |||
resolveddate = resolvedDate | |||
}, true) | |||
-------------------------------------------------------- | -------------------------------------------------------- | ||
-- | -- Categorize feedback | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
| Line 589: | Line 369: | ||
if resolved then | if resolved then | ||
category = | category = | ||
'[[Category:Resolved Feedback]]' | '[[Category:Resolved Feedback]]' | ||
else | else | ||
category = | category = | ||
'[[Category:Unresolved Feedback]]' | '[[Category:Unresolved Feedback]]' | ||
end | end | ||
return card .. category | return card .. category | ||
end | end | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
-- | -- Render feedback list | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
local function renderFeedbackList(results) | local function renderFeedbackList(results) | ||
if not results or #results == 0 then | if not results or #results == 0 then | ||
return '<em>No feedback entries found.</em>' | return '<em>No feedback entries found.</em>' | ||
end | end | ||
local list = {} | local list = {} | ||
for _, row in ipairs(results) do | for _, row in ipairs(results) do | ||
table.insert( | table.insert( | ||
list, | list, | ||
renderCard(row, false) | |||
) | ) | ||
end | end | ||
return table.concat( | |||
return table.concat(list, '\n') | |||
end | end | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
-- | -- View feedback | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
function p. | function p.view(frame) | ||
local | local parent = frame:getParent() | ||
local args | |||
if frame | |||
then | 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 | 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 | end | ||
frame = frame or mw.getCurrentFrame() | |||
-------------------------------------------------------- | -------------------------------------------------------- | ||
-- | -- Determine requested status | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
local | local rawStatus = | ||
args.status | |||
or args[1] | |||
local filterStatus = | |||
rawStatus | |||
and mw.text.trim(rawStatus):lower() | |||
or nil | |||
-------------------------------------------------------- | -------------------------------------------------------- | ||
-- | -- Specific status | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
local | 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 | end | ||
-------------------------------------------------------- | -------------------------------------------------------- | ||
-- All | -- 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 | 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 | |||
end | |||
-------------------------------------------------------- | |||
-- Build tab contents | |||
-------------------------------------------------------- | |||
local allContent | |||
if #allList > 0 then | |||
allContent = | |||
table.concat(allList, '\n') | |||
else | else | ||
allContent = | |||
'<em>No feedback entries found.</em>' | |||
end | end | ||
local unresolvedContent | |||
if #unresolvedList > 0 then | |||
unresolvedContent = | |||
table.concat(unresolvedList, '\n') | |||
else | |||
unresolvedContent = | |||
'<em>No unresolved feedback entries found.</em>' | |||
end | end | ||
local | local resolvedContent | ||
if #resolvedList > 0 then | |||
resolvedContent = | |||
table.concat(resolvedList, '\n') | |||
else | |||
resolvedContent = | |||
'<em>No resolved feedback entries found.</em>' | |||
end | 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 | ||
return p | return p | ||