Jump to content

Module:Feedback: Difference between revisions

From The Petit Planet Wiki
ReisuDesign (talk | contribs)
Undo revision 24119 by ReisuDesign (talk)
Tag: Undo
ReisuDesign (talk | contribs)
No edit summary
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
local p = {}
local p = {}


------------------------------------------------------------
------------------------------------------------------------
-- Helpers
-- HELPERS
------------------------------------------------------------
------------------------------------------------------------


local function isTruthy(value)
local function isTruthy(value)
if value == nil then
if value == nil then
return false
return false
end
end


value =
value = mw.text.trim(tostring(value)):lower()
mw.text.trim(
tostring(value)
):lower()


return value ~= ''
return value == 'true'
and value ~= '0'
or value == 'yes'
and value ~= 'false'
or value == '1'
and value ~= 'no'
or value == 'resolved'
and value ~= 'off'
end
end




local function trim(value)
local function trim(value)
if value == nil then
return ''
end


return mw.text.trim(
return mw.text.trim(tostring(value))
tostring(value or '')
)
 
end
end




local function formatUser(user)
local function formatUser(user)
user = trim(user)


user =
if user == '' then
trim(user)
 
if user == ''
or user:lower() == 'anonymous'
then
return 'Anonymous'
return 'Anonymous'
end
end
Line 50: Line 40:
user
user
)
)
end
end




local function currentSubjectPage()
local function currentSubjectPage()
local title = mw.title.getCurrentTitle()


local title =
if title.subjectPageTitle then
mw.title.getCurrentTitle()
return title.subjectPageTitle.fullText
 
end
return title.subjectPageTitle.fullText


return title.fullText
end
end




local function getBucket()
local function getBucket()
if not (mw.ext and mw.ext.bucket) then
if not (mw.ext and mw.ext.bucket) then
return nil, 'Bucket extension is not available'
return nil
end
end


 
return mw.ext.bucket('feedback')
local bucket =
mw.ext.bucket('feedback')
 
 
if not bucket then
return nil, 'Feedback bucket could not be opened'
end
 
 
return bucket
 
end
end




local function sortNewestFirst(results)
local function sortNewestFirst(entries)
 
if not results then
return {}
end
 
 
table.sort(
table.sort(
results,
entries,
function(a, b)
function(a, b)
local aDate = trim(a.createddate)
local bDate = trim(b.createddate)


local aDate =
return aDate > bDate
trim(a.createddate)
 
local bDate =
trim(b.createddate)
 
 
if aDate == ''
and bDate ~= ''
then
return false
end
 
 
if aDate ~= ''
and bDate == ''
then
return true
end
 
 
if aDate ~= bDate then
return aDate > bDate
end
 
 
return trim(a.id) > trim(b.id)
 
end
end
)
)


 
return entries
return results
 
end
end




local function queryFeedback(
local function queryFeedback(page)
page,
local bucket = getBucket()
status
)
 
local bucket, err =
getBucket()
 


if not bucket then
if not bucket then
return nil, err
return {}
end
end


local query = bucket


local query =
query.select(
bucket.select(
'page',
'page',
'user',
'user',
'status',
'status',
'feedback',
'feedback',
'id',
'id',
'createddate',
'createddate',
'link',
'link',
'image',
'image',
'resolvedby',
'resolvedby',
'resolveddate'
'resolveddate'
)
)
 
 
if page and page ~= '' then
 
query.where(
'page',
page
)
 
end
 
 
if status
and status ~= ''
and status ~= 'all'
then
 
query.where(
'status',
status
)
 
end


 
query.where(
local success, results =
'page',
pcall(
page
function()
return query.run()
end
)
 
 
if not success then
return nil, tostring(results)
end
 
 
return sortNewestFirst(
results or {}
)
)


end
local success, results = pcall(function()
 
return query.run()
end)


------------------------------------------------------------
if not success or not results then
-- Save feedback
return {}
------------------------------------------------------------
 
local function saveFeedback(data)
 
local bucket, err =
getBucket()
 
 
if not bucket then
return false, err
end
end


 
return sortNewestFirst(results)
local success, saveError =
pcall(
function()
 
bucket.put({
 
page =
data.page,
 
user =
data.user,
 
status =
data.status,
 
feedback =
data.feedback,
 
id =
data.id,
 
createddate =
data.createddate,
 
link =
data.link,
 
image =
data.image,
 
resolvedby =
data.resolvedby,
 
resolveddate =
data.resolveddate
 
})
 
end
)
 
 
if not success then
return false, tostring(saveError)
end
 
 
return true
 
end
end




------------------------------------------------------------
------------------------------------------------------------
-- Shared metadata
-- ATTACHMENTS
------------------------------------------------------------
------------------------------------------------------------


local function renderMetadata(
local function renderAttachments(entry)
root,
local wrapper =
entry
mw.html.create('div')
)
:addClass('feedback-attachments')
 
local meta =
root:tag('div')
:addClass(
'feedback-meta'
)
 
 
if entry.user
and entry.user ~= ''
then
 
meta:tag('span')
:addClass(
'feedback-user'
)
:wikitext(
formatUser(
entry.user
)
)
 
end
 
 
if entry.createddate
and entry.createddate ~= ''
then
 
meta:tag('span')
:addClass(
'feedback-meta-date'
)
:wikitext(
' · ' ..
mw.text.nowiki(
entry.createddate
)
)
 
end
 
end
 
 
------------------------------------------------------------
-- Attachments
------------------------------------------------------------
 
local function renderAttachments(
root,
entry
)
 
local link =
trim(entry.link)
 
 
local image =
trim(entry.image)
 
 
if link == ''
and image == ''
then
return
end
 
 
local attachments =
root:tag('div')
:addClass(
'feedback-attachments'
)


local link = trim(entry.link)


if link ~= '' then
if link ~= '' then
 
local linkWrapper =
attachments:tag('span')
wrapper:tag('span')
:addClass(
:addClass('feedback-attachment-link')
'feedback-attachment'
)
linkWrapper:tag('span')
:wikitext(
:addClass('feedback-attachment-icon')
'🔗 ' ..
'[' ..
linkWrapper:wikitext(
link ..
'[' .. link .. ' ' .. mw.text.nowiki(link) .. ']'
' Link]'
)
)
 
end
end


local image = trim(entry.image)


if image ~= '' then
if image ~= '' then
image = mw.ustring.gsub(
image,
'^File:',
''
)


local imageAttachment =
local title =
attachments:tag('span')
mw.title.new('File:' .. image)
:addClass(
'feedback-attachment'
)


 
if title then
imageAttachment:tag('span')
local imageWrapper =
:addClass(
wrapper:tag('span')
'feedback-image-icon'
:addClass('feedback-attachment-image')
imageWrapper:tag('span')
:addClass('feedback-attachment-icon')
imageWrapper:wikitext(
'[' ..
title:fullUrl() ..
' ' ..
mw.text.nowiki(image) ..
']'
)
)
:wikitext('▣')
end
 
 
imageAttachment:tag('span')
:addClass(
'feedback-image-link'
)
:wikitext(
'[[Media:' ..
image ..
'|Picture]]'
)
 
end
end


return wrapper
end
end




------------------------------------------------------------
------------------------------------------------------------
-- Resolution information
-- RESOLUTION
------------------------------------------------------------
------------------------------------------------------------


local function renderResolution(
local function renderResolution(parent, entry)
root,
local resolvedBy = trim(entry.resolvedby)
entry
local resolvedDate = trim(entry.resolveddate)
)
 
local resolvedBy =
trim(entry.resolvedby)
 


local resolvedDate =
if resolvedBy == '' and resolvedDate == '' then
trim(entry.resolveddate)
 
 
if resolvedBy == ''
and resolvedDate == ''
then
return
return
end
end


local resolution =
local resolution =
root:tag('div')
parent:tag('div')
:addClass(
:addClass('feedback-resolution')
'feedback-resolution'
)
 
 
resolution:wikitext(
'↳ Resolved by '
)


resolution:wikitext('Resolved by ')


if resolvedBy ~= '' then
if resolvedBy ~= '' then
resolution:wikitext(
resolution:wikitext(
formatUser(
formatUser(resolvedBy)
resolvedBy
)
)
)
else
else
 
resolution:wikitext('Unknown')
resolution:wikitext(
'Unknown'
)
 
end
end


if resolvedDate ~= '' then
if resolvedDate ~= '' then
resolution:wikitext(
resolution:wikitext(
' on ' ..
' on ' .. mw.text.nowiki(resolvedDate)
mw.text.nowiki(
resolvedDate
)
)
)
end
end
end
end




------------------------------------------------------------
------------------------------------------------------------
-- Resolve control
-- RESOLVE CONTROL
------------------------------------------------------------
------------------------------------------------------------


Line 480: Line 219:
compact
compact
)
)
 
if trim(id) == '' then
if not id
or id == ''
then
return
return
end
end


local wrapper =
parent:tag('div')
:addClass('feedback-resolve')


local control =
local control =
parent:tag('span')
wrapper:tag('span')
:addClass(
:addClass('feedback-resolve-button')
'feedback-resolve-button'
:attr(
'data-feedback-id',
id
)
)
 
:attr(
 
'data-feedback-page',
 
page or ''
control:attr(
'data-feedback-id',
id
)
 
 
if page
and page ~= ''
then
 
control:attr(
'data-feedback-page',
page
)
 
end
 
 
if compact then
 
control
:addClass(
'feedback-resolve-button--compact'
)
)
:attr(
:attr(
Line 531: Line 249:
'aria-label',
'aria-label',
'Resolve feedback'
'Resolve feedback'
)
:attr(
'title',
'Resolve feedback'
)
:wikitext(
'✓'
)
)


else
if compact then
 
control
control
:attr(
:addClass(
'role',
'feedback-resolve-button--compact'
'button'
)
)
:attr(
:attr(
'tabindex',
'title',
'0'
)
:attr(
'aria-label',
'Resolve feedback'
'Resolve feedback'
)
)
:wikitext(
:wikitext('✓')
'Resolve'
else
)
control:wikitext('Resolve')
 
end
end
end
end




------------------------------------------------------------
------------------------------------------------------------
-- Full feedback card
-- FULL FEEDBACK CARD
------------------------------------------------------------
------------------------------------------------------------


local function renderFullCard(
local function renderFullCard(entry)
entry
)
 
local resolved =
local resolved =
entry.status == 'resolved'
trim(entry.status):lower() == 'resolved'
 
 
local id =
trim(entry.id)
 
 
local page =
trim(entry.page)


local id = trim(entry.id)
local page = trim(entry.page)
local feedback = trim(entry.feedback)
local user = trim(entry.user)
local createddate = trim(entry.createddate)


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


if id ~= '' then
if id ~= '' then
root:attr(
root:attr(
'data-feedback-id',
'data-feedback-id',
id
id
)
)
end
end


--------------------------------------------------------
-- Main content column
--------------------------------------------------------
local content =
root:tag('div')
:addClass('feedback-content')


--------------------------------------------------------
--------------------------------------------------------
Line 611: Line 310:


local main =
local main =
root:tag('div')
content:tag('div')
:addClass(
:addClass('feedback-main-line')
'feedback-main-line'
)


--------------------------------------------------------
-- Status
--------------------------------------------------------


main:tag('span')
main:tag('span')
:addClass(
:addClass('feedback-status')
'feedback-status'
)
:addClass(
:addClass(
resolved
resolved
Line 628: Line 326:
:wikitext(
:wikitext(
resolved
resolved
and 'RESOLVED'
and 'Resolved'
or 'UNRESOLVED'
or 'Unresolved'
)
)


--------------------------------------------------------
-- Feedback text
--------------------------------------------------------


main:tag('span')
main:tag('span')
:addClass(
:addClass('feedback-text')
'feedback-text'
)
:wikitext(
:wikitext(
entry.feedback
feedback ~= ''
and feedback
or 'No feedback provided'
or 'No feedback provided'
)
)


--------------------------------------------------------
--------------------------------------------------------
-- Inline metadata
-- Metadata
--------------------------------------------------------
--------------------------------------------------------


local metadata =
local meta =
main:tag('span')
main:tag('span')
:addClass(
:addClass('feedback-inline-meta')
'feedback-inline-meta'
)


 
if user ~= '' then
if entry.user
meta:wikitext(
and entry.user ~= ''
' · ' .. formatUser(user)
then
 
metadata:wikitext(
' · ' ..
formatUser(
entry.user
)
)
)
end
end


 
if createddate ~= '' then
if entry.createddate
meta:wikitext(
and entry.createddate ~= ''
' · ' .. mw.text.nowiki(createddate)
then
 
metadata:wikitext(
' ' ..
mw.text.nowiki(
entry.createddate
)
)
)
end
end


--------------------------------------------------------
--------------------------------------------------------
-- Resolve button
-- Attachments
--------------------------------------------------------
--------------------------------------------------------
 
if not resolved
content:node(
and id ~= ''
    renderAttachments(entry)
then
)
 
renderResolveControl(
main,
id,
page,
false
)
 
end
 


--------------------------------------------------------
--------------------------------------------------------
-- Attachments
-- Resolution information
--------------------------------------------------------
--------------------------------------------------------


renderAttachments(
renderResolution(
root,
content,
entry
entry
)
)


--------------------------------------------------------
--------------------------------------------------------
-- Resolution
-- Missing ID warning
--------------------------------------------------------
--------------------------------------------------------


if resolved then
if id == '' then
 
content:tag('div')
renderResolution(
:addClass('feedback-missing-id')
root,
:wikitext(
entry
'This feedback entry is missing an ID.'
)
)
 
end
end


--------------------------------------------------------
--------------------------------------------------------
-- Missing ID
-- Resolve button
--
-- IMPORTANT:
-- This is outside .feedback-content so it
-- remains on the right.
--------------------------------------------------------
--------------------------------------------------------


if not resolved
if not resolved then
and id == ''
renderResolveControl(
then
root,
 
id,
root:tag('div')
page,
:addClass(
false
'feedback-missing-id'
)
)
:wikitext(
'Add an ID to enable the Resolve button.'
)
 
end
end


return tostring(root)
return tostring(root)
end
end




------------------------------------------------------------
------------------------------------------------------------
-- Compact article card
-- COMPACT CARD
------------------------------------------------------------
------------------------------------------------------------


local function renderCompactCard(
local function renderCompactCard(entry)
entry
return renderFullCard(entry)
)
end


local resolved =
entry.status == 'resolved'


------------------------------------------------------------
-- SAVE FEEDBACK
------------------------------------------------------------


local id =
local function saveFeedback(data)
trim(entry.id)
local bucket = getBucket()


if not bucket then
return false, 'Bucket extension is not available'
end


local page =
local success, err = pcall(function()
trim(entry.page)
bucket.put({
 
page = data.page,
 
user = data.user,
local root =
status = data.status,
mw.html.create('div')
feedback = data.feedback,
:addClass(
id = data.id,
'feedback-page-card'
createddate = data.createddate,
)
link = data.link,
:addClass(
image = data.image,
resolved
resolvedby = data.resolvedby,
and 'feedback-page-card--resolved'
resolveddate = data.resolveddate
or 'feedback-page-card--unresolved'
})
)
end)
 
 
if id ~= '' then
 
root:attr(
'data-feedback-id',
id
)


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


return true
end


local content =
root:tag('div')
:addClass(
'feedback-page-card-content'
)


------------------------------------------------------------
-- MAIN
--
-- Used by Template:Feedback
------------------------------------------------------------


local firstLine =
function p.main(frame)
content:tag('div')
local parent = frame:getParent()
:addClass(
'feedback-page-card-line'
)


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


firstLine:tag('span')
local pageTitle = nil
:addClass(
'feedback-status'
)
:addClass(
resolved
and 'feedback-status--resolved'
or 'feedback-status--unresolved'
)
:wikitext(
resolved
and 'RESOLVED'
or 'UNRESOLVED'
)


if parent and parent.getTitle then
local title = parent:getTitle()


firstLine:tag('span')
if title
:addClass(
and title ~= ''
'feedback-page-card-text'
and not title:find('^Template:')
)
then
:wikitext(
pageTitle = title
entry.feedback
end
or 'No feedback provided'
end
)


return p._main(
args,
pageTitle
)
end


--------------------------------------------------------
-- Metadata
--------------------------------------------------------


local meta =
function p._main(args, pageTitle)
content:tag('div')
local feedback =
:addClass(
trim(args.feedback)
'feedback-page-card-meta'
)


if feedback == '' then
feedback = 'No feedback provided'
end


if entry.user
local user =
and entry.user ~= ''
trim(args.user)
then
 
meta:wikitext(
formatUser(
entry.user
)
)


if user == '' then
user = 'Anonymous'
end
end


local resolved =
isTruthy(args.resolved)


if entry.createddate
local status =
and entry.createddate ~= ''
resolved
then
and 'resolved'
or 'unresolved'


meta:wikitext(
local currentPage =
' · ' ..
trim(pageTitle)
mw.text.nowiki(
entry.createddate
)
)


if currentPage == '' then
currentPage =
mw.title.getCurrentTitle().fullText
end
end


local id =
trim(args.id)


--------------------------------------------------------
local createddate =
-- Resolution
trim(args.createddate)
--------------------------------------------------------


if resolved then
local link =
trim(args.link)


local resolvedBy =
local image =
trim(entry.resolvedby)
trim(args.image)


local resolvedby =
trim(args.resolvedby)


local resolvedDate =
local resolveddate =
trim(entry.resolveddate)
trim(args.resolveddate)


--------------------------------------------------------
-- Save to Bucket
--------------------------------------------------------


if resolvedBy ~= ''
local saved, saveError =
or resolvedDate ~= ''
saveFeedback({
then
page = currentPage,
user = user,
status = status,
feedback = feedback,
id = id,
createddate = createddate,
link = link,
image = image,
resolvedby = resolvedby,
resolveddate = resolveddate
})


local resolution =
if not saved then
content:tag('div')
return
:addClass(
'<strong style="color:red;">Feedback bucket error: '
'feedback-page-card-resolution'
.. mw.text.nowiki(
)
saveError or 'Unknown error'
 
 
resolution:wikitext(
'↳ Resolved by '
)
)
.. '</strong>'
end


--------------------------------------------------------
-- Render
--------------------------------------------------------


if resolvedBy ~= '' then
local card =
renderFullCard({
page = currentPage,
user = user,
status = status,
feedback = feedback,
id = id,
createddate = createddate,
link = link,
image = image,
resolvedby = resolvedby,
resolveddate = resolveddate
})


resolution:wikitext(
--------------------------------------------------------
formatUser(
-- Categories
resolvedBy
--------------------------------------------------------
)
)


else
local category


resolution:wikitext(
if resolved then
'Unknown'
category =
)
'[[Category:Resolved Feedback]]'
else
category =
'[[Category:Unresolved Feedback]]'
end


end
if id == '' then
category =
category ..
'[[Category:Feedbacks Missing ID]]'
end


return card .. category
end


if resolvedDate ~= '' then


resolution:wikitext(
------------------------------------------------------------
' · ' ..
-- RENDER FEEDBACK LIST
mw.text.nowiki(
------------------------------------------------------------
resolvedDate
)
)


end
local function renderFeedbackList(results)
if not results or #results == 0 then
return '<em>No feedback entries found.</em>'
end


end
local list = {}
 
else
 
if id ~= '' then
 
renderResolveControl(
root,
id,
page,
true
)
 
end


for _, row in ipairs(results) do
table.insert(
list,
renderFullCard(row)
)
end
end


 
return table.concat(
--------------------------------------------------------
list,
-- Attachments
'\n'
--------------------------------------------------------
 
renderAttachments(
content,
entry
)
)
return tostring(root)
end
end




------------------------------------------------------------
------------------------------------------------------------
-- Page feedback section
-- PAGE FEEDBACK
------------------------------------------------------------
------------------------------------------------------------


function p.page(frame)
function p.page(frame)
local page =
local page =
currentSubjectPage()
currentSubjectPage()


 
local entries =
local allEntries =
queryFeedback(page)
queryFeedback(
page
)
 
 
if not allEntries then
return ''
end
 
 
if #allEntries == 0 then
return ''
end
 


local unresolved = {}
local unresolved = {}
local resolved = {}
local resolved = {}


 
for _, entry in ipairs(entries) do
for _, row in ipairs(allEntries) do
if trim(entry.status):lower()
 
== 'resolved'
if row.status == 'resolved' then
then
 
table.insert(
table.insert(
resolved,
resolved,
row
entry
)
)
else
else
table.insert(
table.insert(
unresolved,
unresolved,
row
entry
)
)
end
end
end
end


local root =
local root =
mw.html.create('section')
mw.html.create('section')
:addClass(
'feedback-page-section'
)
:attr(
:attr(
'id',
'id',
'feedback-section'
'feedback-section'
)
)
:attr(
:addClass('feedback-page')
'data-unresolved-count',
#unresolved
)
:attr(
'data-resolved-count',
#resolved
)
 


--------------------------------------------------------
--------------------------------------------------------
Line 1,034: Line 674:


root:tag('h2')
root:tag('h2')
:addClass(
:addClass('feedback-page-title')
'feedback-page-heading'
:wikitext('Feedbacks')
)
:wikitext(
'Feedbacks'
)
 


--------------------------------------------------------
--------------------------------------------------------
Line 1,048: Line 683:
local filters =
local filters =
root:tag('div')
root:tag('div')
:addClass(
:addClass('feedback-filters')
'feedback-page-filters'
)


 
local function addFilter(
local unresolvedFilter =
label,
filters:tag('span')
value,
:addClass(
active
'feedback-page-filter'
)
)
return filters:tag('button')
:addClass(
'feedback-page-filter--active'
)
:attr(
:attr(
'role',
'type',
'button'
'button'
)
)
:attr(
:attr(
'tabindex',
'data-feedback-filter',
'0'
value
)
)
:attr(
:addClass('feedback-filter')
'data-feedback-filter',
:addClass(
'unresolved'
active
and 'feedback-filter--active'
or nil
)
)
:wikitext(label)
end


addFilter(
'Unresolved',
'unresolved',
true
)


unresolvedFilter:wikitext(
addFilter(
'Unresolved '
'Resolved',
'resolved',
false
)
)


addFilter(
'All',
'all',
false
)
--------------------------------------------------------
-- Unresolved
--------------------------------------------------------


unresolvedFilter:tag('span')
local unresolvedList =
:addClass(
root:tag('div')
'feedback-page-filter-count'
:addClass(
)
'feedback-page-list'
:wikitext(
)
tostring(
:addClass(
#unresolved
'feedback-page-list--unresolved'
)
)
)
if #resolved > 0 then
local resolvedFilter =
filters:tag('span')
:addClass(
'feedback-page-filter'
)
:attr(
'role',
'button'
)
:attr(
'tabindex',
'0'
)
:attr(
'data-feedback-filter',
'resolved'
)
resolvedFilter:wikitext(
'Resolved '
)
resolvedFilter:tag('span')
:addClass(
:addClass(
'feedback-page-filter-count'
'feedback-page-list--visible'
)
)
if #unresolved == 0 then
unresolvedList:tag('div')
:addClass('feedback-empty')
:wikitext(
:wikitext(
tostring(
'No unresolved feedback.'
#resolved
)
)
)
 
else
for _, entry in ipairs(unresolved) do
unresolvedList:wikitext(
renderCompactCard(entry)
)
end
end
end


--------------------------------------------------------
--------------------------------------------------------
-- Unresolved list
-- Resolved
--------------------------------------------------------
--------------------------------------------------------


local unresolvedList =
local resolvedList =
root:tag('div')
root:tag('div')
:addClass(
:addClass(
Line 1,140: Line 766:
)
)
:addClass(
:addClass(
'feedback-page-list--unresolved'
'feedback-page-list--resolved'
)
)


 
if #resolved == 0 then
if #unresolved > 0 then
resolvedList:tag('div')
 
:addClass('feedback-empty')
for _, row in ipairs(unresolved) do
:wikitext(
 
'No resolved feedback.'
unresolvedList:node(
renderCompactCard(
row
)
)
)
end
else
else
 
for _, entry in ipairs(resolved) do
unresolvedList:tag('div')
resolvedList:wikitext(
:addClass(
renderCompactCard(entry)
'feedback-page-empty'
)
)
:wikitext(
end
'No unresolved feedback.'
)
 
end
end


--------------------------------------------------------
--------------------------------------------------------
-- Resolved list
-- All
--------------------------------------------------------
--------------------------------------------------------


if #resolved > 0 then
local allList =
root:tag('div')
:addClass(
'feedback-page-list'
)
:addClass(
'feedback-page-list--all'
)


local resolvedList =
if #entries == 0 then
root:tag('div')
allList:tag('div')
:addClass(
:addClass('feedback-empty')
'feedback-page-list'
:wikitext(
)
'No feedback.'
:addClass(
)
'feedback-page-list--resolved'
else
)
for _, entry in ipairs(entries) do
 
allList:wikitext(
 
renderCompactCard(entry)
for _, row in ipairs(resolved) do
 
resolvedList:node(
renderCompactCard(
row
)
)
)
end
end
end
end


return tostring(root)
return tostring(root)
end
end




------------------------------------------------------------
------------------------------------------------------------
-- Feedback counts
-- COUNTS
------------------------------------------------------------
------------------------------------------------------------


function p.counts(frame)
function p.counts(frame)
local page =
currentSubjectPage()
local entries =
local entries =
queryFeedback(
queryFeedback(
page
currentSubjectPage()
)
)
if not entries then
return ''
end


local unresolved = 0
local unresolved = 0
local resolved = 0
local resolved = 0


 
for _, entry in ipairs(entries) do
for _, row in ipairs(entries) do
if trim(entry.status):lower()
 
== 'resolved'
if row.status == 'resolved' then
then
resolved = resolved + 1
resolved = resolved + 1
else
else
unresolved = unresolved + 1
unresolved = unresolved + 1
end
end
end
end


return tostring(
return tostring(
mw.html.create('span')
mw.html.create('span')
:addClass(
:addClass('feedback-count-data')
'feedback-count-data'
)
:attr(
:attr(
'data-unresolved',
'data-unresolved',
Line 1,253: Line 848:
resolved
resolved
)
)
)
)
end
end




------------------------------------------------------------
------------------------------------------------------------
-- Main / individual feedback
-- TABLE / DASHBOARD
------------------------------------------------------------
------------------------------------------------------------


function p.main(frame)
local function renderTable(entries)
 
if not entries or #entries == 0 then
local parent =
return '<em>No feedback entries found.</em>'
frame:getParent()
 
 
local args =
parent
and parent.args
or frame.args
 
 
local feedback =
args.feedback
or 'No feedback provided'
 
 
local user =
args.user
or 'Anonymous'
 
 
local id =
trim(
args.id
)
 
 
local createdDate =
trim(
args.createddate
)
 
 
local link =
trim(
args.link
)
 
 
local image =
trim(
args.image
)
 
 
local resolvedBy =
trim(
args.resolvedby
)
 
 
local resolvedDate =
trim(
args.resolveddate
)
 
 
local resolved =
isTruthy(
args.resolved
)
 
 
local status =
resolved
and 'resolved'
or 'unresolved'
 
 
local page =
currentSubjectPage()
 
 
local saved, saveError =
saveFeedback({
 
page =
page,
 
user =
user,
 
status =
status,
 
feedback =
feedback,
 
id =
id,
 
createddate =
createdDate,
 
link =
link,
 
image =
image,
 
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
end


 
local table =
local card =
renderFullCard({
 
page =
page,
 
user =
user,
 
status =
status,
 
feedback =
feedback,
 
id =
id,
 
createddate =
createdDate,
 
link =
link,
 
image =
image,
 
resolvedby =
resolvedBy,
 
resolveddate =
resolvedDate
 
})
 
 
local categories = {}
 
 
if resolved then
 
table.insert(
categories,
'[[Category:Resolved Feedback]]'
)
 
else
 
table.insert(
categories,
'[[Category:Unresolved Feedback]]'
)
 
end
 
 
if id == '' then
 
table.insert(
categories,
'[[Category:Feedbacks Missing ID]]'
)
 
end
 
 
return card ..
table.concat(
categories
)
 
end
 
 
------------------------------------------------------------
-- Feedback table
------------------------------------------------------------
 
local function renderTable(
entries,
includeResolved
)
 
local tableRoot =
mw.html.create('table')
mw.html.create('table')
:addClass(
:addClass('feedback-table')
'feedback-table'
)


local head =
table:tag('thead')
:tag('tr')


local header =
head:tag('th')
tableRoot:tag('tr')
:wikitext('Status')


head:tag('th')
:wikitext('Page')


if includeResolved then
head:tag('th')
:wikitext('Feedback')


header:tag('th')
head:tag('th')
:wikitext(
:wikitext('User')
'Status'
)


end
head:tag('th')
:wikitext('Date')


local body =
table:tag('tbody')


header:tag('th')
for _, entry in ipairs(entries) do
:wikitext(
'Page'
)
 
 
header:tag('th')
:wikitext(
'Username'
)
 
 
header:tag('th')
:wikitext(
'Date'
)
 
 
header:tag('th')
:wikitext(
'Feedback'
)
 
 
if includeResolved then
 
header:tag('th')
:wikitext(
'Resolved by'
)
 
 
header:tag('th')
:wikitext(
'Resolved date'
)
 
end
 
 
if #entries == 0 then
 
local row =
local row =
tableRoot:tag('tr')
body:tag('tr')
 
 
local colspan =
includeResolved
and '7'
or '4'


local resolved =
trim(entry.status):lower()
== 'resolved'


row:tag('td')
row:tag('td')
:attr(
'colspan',
colspan
)
:addClass(
:addClass(
'feedback-table-empty'
resolved
and 'feedback-table-status--resolved'
or 'feedback-table-status--unresolved'
)
)
:wikitext(
:wikitext(
'No feedback entries found.'
resolved
and 'Resolved'
or 'Unresolved'
)
)
return tostring(
tableRoot
)
end
for _, entry in ipairs(entries) do
local row =
tableRoot:tag('tr')
:addClass(
entry.status == 'resolved'
and 'feedback-table-row--resolved'
or 'feedback-table-row--unresolved'
)
if includeResolved then
row:tag('td')
:addClass(
'feedback-table-status'
)
:wikitext(
entry.status == 'resolved'
and 'Resolved'
or 'Unresolved'
)
end


row:tag('td')
row:tag('td')
:addClass(
:addClass('feedback-table-page')
'feedback-table-page'
)
:wikitext(
:wikitext(
'[[' ..
entry.page or ''
entry.page ..
']]'
)
)


row:tag('td')
row:tag('td')
:addClass(
:addClass('feedback-table-text')
'feedback-table-user'
)
:wikitext(
:wikitext(
formatUser(
entry.feedback or ''
entry.user
)
)
)


row:tag('td')
row:tag('td')
:addClass(
'feedback-table-date'
)
:wikitext(
:wikitext(
mw.text.nowiki(
formatUser(entry.user)
entry.createddate
or '—'
)
)
)


row:tag('td')
row:tag('td')
:addClass(
'feedback-table-feedback'
)
:wikitext(
:wikitext(
entry.feedback
entry.createddate or ''
or ''
)
)
if includeResolved then
row:tag('td')
:addClass(
'feedback-table-resolved-by'
)
:wikitext(
entry.resolvedby
and formatUser(
entry.resolvedby
)
or '—'
)
row:tag('td')
:addClass(
'feedback-table-resolved-date'
)
:wikitext(
entry.resolveddate
and mw.text.nowiki(
entry.resolveddate
)
or '—'
)
end
end
end


 
return tostring(table)
return tostring(
tableRoot
)
 
end
end


------------------------------------------------------------
-- Global feedback page
------------------------------------------------------------


function p.view(frame)
function p.view(frame)
local parent =
local parent =
frame:getParent()
frame:getParent()


local args


local args =
if frame
parent
and frame.args
and parent.args
and frame.args.status
or frame.args
then
args = frame.args


elseif parent
and parent.args
and parent.args.status
then
args = parent.args


local status =
elseif frame
trim(
and frame.args
args.status or ''
then
):lower()
args = frame.args
 
 
--------------------------------------------------------
-- Specific table
--------------------------------------------------------


if status == 'resolved'
elseif parent
or status == 'unresolved'
and parent.args
then
then
args = parent.args


local entries =
else
queryFeedback(
args = {}
nil,
end
status
)


return p._view(
args,
frame
)
end


if not entries then
return ''
end


function p._view(args, frame)
local bucket =
getBucket()


return renderTable(
if not bucket then
entries,
return
false
'<em>Bucket extension is not available.</em>'
)
end


end
local rawStatus =
args.status
or args[1]


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


--------------------------------------------------------
--------------------------------------------------------
-- Dashboard
-- Query
--------------------------------------------------------
--------------------------------------------------------


local unresolved =
local query =
queryFeedback(
bucket
nil,
'unresolved'
)


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


local all =
if filterStatus == 'resolved'
queryFeedback(
or filterStatus == 'unresolved'
nil,
then
'all'
query.where(
'status',
filterStatus
)
)
if not unresolved then
unresolved = {}
end
end


local success, entries =
pcall(function()
return query.run()
end)


if not all then
if not success or not entries then
all = {}
return
'<em>Could not load feedback.</em>'
end
end


entries =
sortNewestFirst(entries)


local root =
--------------------------------------------------------
mw.html.create('div')
-- Specific status
:addClass(
--------------------------------------------------------
'feedback-dashboard'
)


 
if filterStatus == 'resolved'
root:tag('h2')
or filterStatus == 'unresolved'
:addClass(
then
'feedback-dashboard-heading'
return renderFeedbackList(
)
entries
:wikitext(
'Unresolved Feedback'
)
)
end


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


root:node(
return renderTable(entries)
renderTable(
unresolved,
false
)
)
 
 
root:tag('h2')
:addClass(
'feedback-dashboard-heading'
)
:wikitext(
'All Feedback'
)
 
 
root:node(
renderTable(
all,
true
)
)
 
 
return tostring(
root
)
 
end
end


------------------------------------------------------------
-- Aliases
------------------------------------------------------------


p.table = p.view
p.table = p.view


return p
return p

Latest revision as of 13:57, 16 September 2026

Module documentation

This module implements {{Feedback}}.

local p = {}

------------------------------------------------------------
-- HELPERS
------------------------------------------------------------

local function isTruthy(value)
	if value == nil then
		return false
	end

	value = mw.text.trim(tostring(value)):lower()

	return value == 'true'
		or value == 'yes'
		or value == '1'
		or value == 'resolved'
end


local function trim(value)
	if value == nil then
		return ''
	end

	return mw.text.trim(tostring(value))
end


local function formatUser(user)
	user = trim(user)

	if user == '' then
		return 'Anonymous'
	end

	return string.format(
		'[[User:%s|%s]]',
		user,
		user
	)
end


local function currentSubjectPage()
	local title = mw.title.getCurrentTitle()

	if title.subjectPageTitle then
		return title.subjectPageTitle.fullText
	end

	return title.fullText
end


local function getBucket()
	if not (mw.ext and mw.ext.bucket) then
		return nil
	end

	return mw.ext.bucket('feedback')
end


local function sortNewestFirst(entries)
	table.sort(
		entries,
		function(a, b)
			local aDate = trim(a.createddate)
			local bDate = trim(b.createddate)

			return aDate > bDate
		end
	)

	return entries
end


local function queryFeedback(page)
	local bucket = getBucket()

	if not bucket then
		return {}
	end

	local query = bucket

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

	query.where(
		'page',
		page
	)

	local success, results = pcall(function()
		return query.run()
	end)

	if not success or not results then
		return {}
	end

	return sortNewestFirst(results)
end


------------------------------------------------------------
-- ATTACHMENTS
------------------------------------------------------------

local function renderAttachments(entry)
	local wrapper =
		mw.html.create('div')
			:addClass('feedback-attachments')

	local link = trim(entry.link)

	if link ~= '' then
		local linkWrapper =
			wrapper:tag('span')
				:addClass('feedback-attachment-link')
	
		linkWrapper:tag('span')
			:addClass('feedback-attachment-icon')
	
		linkWrapper:wikitext(
			'[' .. link .. ' ' .. mw.text.nowiki(link) .. ']'
		)
	end

	local image = trim(entry.image)

	if image ~= '' then
		image = mw.ustring.gsub(
			image,
			'^File:',
			''
		)

		local title =
			mw.title.new('File:' .. image)

		if title then
			local imageWrapper =
				wrapper:tag('span')
					:addClass('feedback-attachment-image')
		
			imageWrapper:tag('span')
				:addClass('feedback-attachment-icon')
		
			imageWrapper:wikitext(
				'[' ..
				title:fullUrl() ..
				' ' ..
				mw.text.nowiki(image) ..
				']'
			)
		end
	end

	return wrapper
end


------------------------------------------------------------
-- RESOLUTION
------------------------------------------------------------

local function renderResolution(parent, entry)
	local resolvedBy = trim(entry.resolvedby)
	local resolvedDate = trim(entry.resolveddate)

	if resolvedBy == '' and resolvedDate == '' then
		return
	end

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

	resolution:wikitext('Resolved by ')

	if resolvedBy ~= '' then
		resolution:wikitext(
			formatUser(resolvedBy)
		)
	else
		resolution:wikitext('Unknown')
	end

	if resolvedDate ~= '' then
		resolution:wikitext(
			' on ' .. mw.text.nowiki(resolvedDate)
		)
	end
end


------------------------------------------------------------
-- RESOLVE CONTROL
------------------------------------------------------------

local function renderResolveControl(
	parent,
	id,
	page,
	compact
)
	if trim(id) == '' then
		return
	end

	local wrapper =
		parent:tag('div')
			:addClass('feedback-resolve')

	local control =
		wrapper:tag('span')
			:addClass('feedback-resolve-button')
			:attr(
				'data-feedback-id',
				id
			)
			:attr(
				'data-feedback-page',
				page or ''
			)
			:attr(
				'role',
				'button'
			)
			:attr(
				'tabindex',
				'0'
			)
			:attr(
				'aria-label',
				'Resolve feedback'
			)

	if compact then
		control
			:addClass(
				'feedback-resolve-button--compact'
			)
			:attr(
				'title',
				'Resolve feedback'
			)
			:wikitext('✓')
	else
		control:wikitext('Resolve')
	end
end


------------------------------------------------------------
-- FULL FEEDBACK CARD
------------------------------------------------------------

local function renderFullCard(entry)
	local resolved =
		trim(entry.status):lower() == 'resolved'

	local id = trim(entry.id)
	local page = trim(entry.page)
	local feedback = trim(entry.feedback)
	local user = trim(entry.user)
	local createddate = trim(entry.createddate)

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

	if id ~= '' then
		root:attr(
			'data-feedback-id',
			id
		)
	end

	--------------------------------------------------------
	-- Main content column
	--------------------------------------------------------

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

	--------------------------------------------------------
	-- Main line
	--------------------------------------------------------

	local main =
		content:tag('div')
			:addClass('feedback-main-line')

	--------------------------------------------------------
	-- Status
	--------------------------------------------------------

	main:tag('span')
		:addClass('feedback-status')
		:addClass(
			resolved
				and 'feedback-status--resolved'
				or 'feedback-status--unresolved'
		)
		:wikitext(
			resolved
				and 'Resolved'
				or 'Unresolved'
		)

	--------------------------------------------------------
	-- Feedback text
	--------------------------------------------------------

	main:tag('span')
		:addClass('feedback-text')
		:wikitext(
			feedback ~= ''
				and feedback
				or 'No feedback provided'
		)

	--------------------------------------------------------
	-- Metadata
	--------------------------------------------------------

	local meta =
		main:tag('span')
			:addClass('feedback-inline-meta')

	if user ~= '' then
		meta:wikitext(
			' · ' .. formatUser(user)
		)
	end

	if createddate ~= '' then
		meta:wikitext(
			' · ' .. mw.text.nowiki(createddate)
		)
	end

	--------------------------------------------------------
	-- Attachments
	--------------------------------------------------------
	
	content:node(
	    renderAttachments(entry)
	)

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

	renderResolution(
		content,
		entry
	)

	--------------------------------------------------------
	-- Missing ID warning
	--------------------------------------------------------

	if id == '' then
		content:tag('div')
			:addClass('feedback-missing-id')
			:wikitext(
				'This feedback entry is missing an ID.'
			)
	end

	--------------------------------------------------------
	-- Resolve button
	--
	-- IMPORTANT:
	-- This is outside .feedback-content so it
	-- remains on the right.
	--------------------------------------------------------

	if not resolved then
		renderResolveControl(
			root,
			id,
			page,
			false
		)
	end

	return tostring(root)
end


------------------------------------------------------------
-- COMPACT CARD
------------------------------------------------------------

local function renderCompactCard(entry)
	return renderFullCard(entry)
end


------------------------------------------------------------
-- SAVE FEEDBACK
------------------------------------------------------------

local function saveFeedback(data)
	local bucket = getBucket()

	if not bucket then
		return false, 'Bucket extension is not available'
	end

	local success, err = pcall(function()
		bucket.put({
			page = data.page,
			user = data.user,
			status = data.status,
			feedback = data.feedback,
			id = data.id,
			createddate = data.createddate,
			link = data.link,
			image = data.image,
			resolvedby = data.resolvedby,
			resolveddate = data.resolveddate
		})
	end)

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

	return true
end


------------------------------------------------------------
-- MAIN
--
-- Used by Template:Feedback
------------------------------------------------------------

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 =
		trim(args.feedback)

	if feedback == '' then
		feedback = 'No feedback provided'
	end

	local user =
		trim(args.user)

	if user == '' then
		user = 'Anonymous'
	end

	local resolved =
		isTruthy(args.resolved)

	local status =
		resolved
			and 'resolved'
			or 'unresolved'

	local currentPage =
		trim(pageTitle)

	if currentPage == '' then
		currentPage =
			mw.title.getCurrentTitle().fullText
	end

	local id =
		trim(args.id)

	local createddate =
		trim(args.createddate)

	local link =
		trim(args.link)

	local image =
		trim(args.image)

	local resolvedby =
		trim(args.resolvedby)

	local resolveddate =
		trim(args.resolveddate)

	--------------------------------------------------------
	-- Save to Bucket
	--------------------------------------------------------

	local saved, saveError =
		saveFeedback({
			page = currentPage,
			user = user,
			status = status,
			feedback = feedback,
			id = id,
			createddate = createddate,
			link = link,
			image = image,
			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
	--------------------------------------------------------

	local card =
		renderFullCard({
			page = currentPage,
			user = user,
			status = status,
			feedback = feedback,
			id = id,
			createddate = createddate,
			link = link,
			image = image,
			resolvedby = resolvedby,
			resolveddate = resolveddate
		})

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

	local category

	if resolved then
		category =
			'[[Category:Resolved Feedback]]'
	else
		category =
			'[[Category:Unresolved Feedback]]'
	end

	if id == '' then
		category =
			category ..
			'[[Category:Feedbacks Missing ID]]'
	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,
			renderFullCard(row)
		)
	end

	return table.concat(
		list,
		'\n'
	)
end


------------------------------------------------------------
-- PAGE FEEDBACK
------------------------------------------------------------

function p.page(frame)
	local page =
		currentSubjectPage()

	local entries =
		queryFeedback(page)

	local unresolved = {}
	local resolved = {}

	for _, entry in ipairs(entries) do
		if trim(entry.status):lower()
			== 'resolved'
		then
			table.insert(
				resolved,
				entry
			)
		else
			table.insert(
				unresolved,
				entry
			)
		end
	end

	local root =
		mw.html.create('section')
			:attr(
				'id',
				'feedback-section'
			)
			:addClass('feedback-page')

	--------------------------------------------------------
	-- Heading
	--------------------------------------------------------

	root:tag('h2')
		:addClass('feedback-page-title')
		:wikitext('Feedbacks')

	--------------------------------------------------------
	-- Filters
	--------------------------------------------------------

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

	local function addFilter(
		label,
		value,
		active
	)
		return filters:tag('button')
			:attr(
				'type',
				'button'
			)
			:attr(
				'data-feedback-filter',
				value
			)
			:addClass('feedback-filter')
			:addClass(
				active
					and 'feedback-filter--active'
					or nil
			)
			:wikitext(label)
	end

	addFilter(
		'Unresolved',
		'unresolved',
		true
	)

	addFilter(
		'Resolved',
		'resolved',
		false
	)

	addFilter(
		'All',
		'all',
		false
	)

	--------------------------------------------------------
	-- Unresolved
	--------------------------------------------------------

	local unresolvedList =
		root:tag('div')
			:addClass(
				'feedback-page-list'
			)
			:addClass(
				'feedback-page-list--unresolved'
			)
			:addClass(
				'feedback-page-list--visible'
			)

	if #unresolved == 0 then
		unresolvedList:tag('div')
			:addClass('feedback-empty')
			:wikitext(
				'No unresolved feedback.'
			)
	else
		for _, entry in ipairs(unresolved) do
			unresolvedList:wikitext(
				renderCompactCard(entry)
			)
		end
	end

	--------------------------------------------------------
	-- Resolved
	--------------------------------------------------------

	local resolvedList =
		root:tag('div')
			:addClass(
				'feedback-page-list'
			)
			:addClass(
				'feedback-page-list--resolved'
			)

	if #resolved == 0 then
		resolvedList:tag('div')
			:addClass('feedback-empty')
			:wikitext(
				'No resolved feedback.'
			)
	else
		for _, entry in ipairs(resolved) do
			resolvedList:wikitext(
				renderCompactCard(entry)
			)
		end
	end

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

	local allList =
		root:tag('div')
			:addClass(
				'feedback-page-list'
			)
			:addClass(
				'feedback-page-list--all'
			)

	if #entries == 0 then
		allList:tag('div')
			:addClass('feedback-empty')
			:wikitext(
				'No feedback.'
			)
	else
		for _, entry in ipairs(entries) do
			allList:wikitext(
				renderCompactCard(entry)
			)
		end
	end

	return tostring(root)
end


------------------------------------------------------------
-- COUNTS
------------------------------------------------------------

function p.counts(frame)
	local entries =
		queryFeedback(
			currentSubjectPage()
		)

	local unresolved = 0
	local resolved = 0

	for _, entry in ipairs(entries) do
		if trim(entry.status):lower()
			== 'resolved'
		then
			resolved = resolved + 1
		else
			unresolved = unresolved + 1
		end
	end

	return tostring(
		mw.html.create('span')
			:addClass('feedback-count-data')
			:attr(
				'data-unresolved',
				unresolved
			)
			:attr(
				'data-resolved',
				resolved
			)
	)
end


------------------------------------------------------------
-- TABLE / DASHBOARD
------------------------------------------------------------

local function renderTable(entries)
	if not entries or #entries == 0 then
		return '<em>No feedback entries found.</em>'
	end

	local table =
		mw.html.create('table')
			:addClass('feedback-table')

	local head =
		table:tag('thead')
			:tag('tr')

	head:tag('th')
		:wikitext('Status')

	head:tag('th')
		:wikitext('Page')

	head:tag('th')
		:wikitext('Feedback')

	head:tag('th')
		:wikitext('User')

	head:tag('th')
		:wikitext('Date')

	local body =
		table:tag('tbody')

	for _, entry in ipairs(entries) do
		local row =
			body:tag('tr')

		local resolved =
			trim(entry.status):lower()
				== 'resolved'

		row:tag('td')
			:addClass(
				resolved
					and 'feedback-table-status--resolved'
					or 'feedback-table-status--unresolved'
			)
			:wikitext(
				resolved
					and 'Resolved'
					or 'Unresolved'
			)

		row:tag('td')
			:addClass('feedback-table-page')
			:wikitext(
				entry.page or ''
			)

		row:tag('td')
			:addClass('feedback-table-text')
			:wikitext(
				entry.feedback or ''
			)

		row:tag('td')
			:wikitext(
				formatUser(entry.user)
			)

		row:tag('td')
			:wikitext(
				entry.createddate or ''
			)
	end

	return tostring(table)
end


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)
	local bucket =
		getBucket()

	if not bucket then
		return
			'<em>Bucket extension is not available.</em>'
	end

	local rawStatus =
		args.status
		or args[1]

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

	--------------------------------------------------------
	-- Query
	--------------------------------------------------------

	local query =
		bucket

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

	if filterStatus == 'resolved'
		or filterStatus == 'unresolved'
	then
		query.where(
			'status',
			filterStatus
		)
	end

	local success, entries =
		pcall(function()
			return query.run()
		end)

	if not success or not entries then
		return
			'<em>Could not load feedback.</em>'
	end

	entries =
		sortNewestFirst(entries)

	--------------------------------------------------------
	-- Specific status
	--------------------------------------------------------

	if filterStatus == 'resolved'
		or filterStatus == 'unresolved'
	then
		return renderFeedbackList(
			entries
		)
	end

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

	return renderTable(entries)
end


p.table = p.view

return p