Jump to content
Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
If you make an edit, a temporary account will be created for you.

Module:Feedback: Difference between revisions

From The Petit Planet Wiki
ReisuDesign (talk | contribs)
No edit summary
ReisuDesign (talk | contribs)
No edit summary
 
(13 intermediate revisions by the same user not shown)
Line 2: Line 2:


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


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


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


return v ~= ''
return value == 'true'
and v ~= '0'
or value == 'yes'
and v ~= 'false'
or value == '1'
and v ~= 'no'
or value == 'resolved'
and v ~= 'off'
end
 
 
local function trim(value)
if value == nil then
return ''
end
 
return mw.text.trim(tostring(value))
end
end




local function formatUser(user)
local function formatUser(user)
user = mw.text.trim(tostring(user or ''))
user = trim(user)


if user == '' or user:lower() == 'anonymous' then
if user == '' then
return 'Anonymous'
return 'Anonymous'
end
end
Line 32: Line 40:
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
end




------------------------------------------------------------
------------------------------------------------------------
-- Feedback card
-- ATTACHMENTS
------------------------------------------------------------
------------------------------------------------------------


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


local resolved =
------------------------------------------------------------
(entry.status == 'resolved')
-- RESOLUTION
------------------------------------------------------------
 
local function renderResolution(parent, entry)
local resolvedBy = trim(entry.resolvedby)
local resolvedDate = trim(entry.resolveddate)


local user =
if resolvedBy == '' and resolvedDate == '' then
entry.user
return
or 'Anonymous'
end


local feedback =
local resolution =
entry.feedback
parent:tag('div')
or 'No feedback provided'
:addClass('feedback-resolution')


local page =
resolution:wikitext('Resolved by ')
entry.page


local id =
if resolvedBy ~= '' then
mw.text.trim(
resolution:wikitext(
tostring(entry.id or '')
formatUser(resolvedBy)
)
)
else
resolution:wikitext('Unknown')
end


local resolvedBy =
if resolvedDate ~= '' then
mw.text.trim(
resolution:wikitext(
tostring(entry.resolvedby or '')
' on ' .. mw.text.nowiki(resolvedDate)
)
)
end
end
------------------------------------------------------------
-- RESOLVE CONTROL
------------------------------------------------------------


local resolvedDate =
local function renderResolveControl(
mw.text.trim(
parent,
tostring(entry.resolveddate or '')
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 =
local root =
Line 78: Line 289:
or 'feedback-box--unresolved'
or 'feedback-box--unresolved'
)
)
if id ~= '' then
root:attr(
'data-feedback-id',
id
)
end


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


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


if page and page ~= '' then
--------------------------------------------------------
 
-- Main line
meta:tag('span')
--------------------------------------------------------
:addClass('feedback-page')
:wikitext(
string.format(
'[[%s]]',
page
)
)


meta:tag('span')
local main =
:addClass('feedback-separator')
content:tag('div')
:wikitext(' • ')
:addClass('feedback-main-line')


end
--------------------------------------------------------
 
-- Status
meta:tag('span')
--------------------------------------------------------
:addClass('feedback-user')
:wikitext(
formatUser(user)
)


meta:tag('span')
main:tag('span')
:addClass('feedback-separator')
:wikitext(' • ')
 
meta:tag('span')
:addClass('feedback-status')
:addClass('feedback-status')
:addClass(
:addClass(
Line 128: Line 331:


--------------------------------------------------------
--------------------------------------------------------
-- Feedback body
-- Feedback text
--------------------------------------------------------
--------------------------------------------------------


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


--------------------------------------------------------
--------------------------------------------------------
-- Resolution information
-- Metadata
--------------------------------------------------------
--------------------------------------------------------


if resolved and resolvedBy ~= '' then
local meta =
main:tag('span')
:addClass('feedback-inline-meta')


local resolution =
if user ~= '' then
root:tag('div')
meta:wikitext(
:addClass('feedback-resolution')
' · ' .. formatUser(user)
)
end


resolution:wikitext(
if createddate ~= '' then
'Resolved by '
meta:wikitext(
.. formatUser(resolvedBy)
' · ' .. mw.text.nowiki(createddate)
)
)
if resolvedDate ~= '' then
resolution:wikitext(
' on '
.. mw.text.nowiki(resolvedDate)
)
end
end
end


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


if showInstruction and not resolved then
--------------------------------------------------------
-- Resolution information
--------------------------------------------------------


local instruction =
renderResolution(
root:tag('div')
content,
:addClass('feedback-instruction')
entry
)


if id ~= '' then
--------------------------------------------------------
-- Missing ID warning
--------------------------------------------------------


instruction:wikitext(
if id == '' then
'If this feedback has been resolved, click the button below. '
content:tag('div')
:addClass('feedback-missing-id')
:wikitext(
'This feedback entry is missing an ID.'
)
)
end


instruction:tag('span')
--------------------------------------------------------
:addClass('feedback-resolve-button')
-- Resolve button
:attr('role', 'button')
--
:attr('tabindex', '0')
-- IMPORTANT:
:attr('data-feedback-id', id)
-- This is outside .feedback-content so it
:wikitext('Resolve')
-- remains on the right.
--------------------------------------------------------


else
if not resolved then
 
renderResolveControl(
instruction:wikitext(
root,
'Add an ID to this feedback to enable the Resolve button.'
id,
)
page,
 
false
end
)
end
end


Line 197: Line 413:


------------------------------------------------------------
------------------------------------------------------------
-- Save feedback
-- COMPACT CARD
------------------------------------------------------------
------------------------------------------------------------


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


if not (mw.ext and mw.ext.bucket) then
------------------------------------------------------------
return false, 'Bucket extension is not available'
-- SAVE FEEDBACK
end
------------------------------------------------------------


local bucket =
local function saveFeedback(data)
mw.ext.bucket('feedback')
local bucket = getBucket()


if not bucket then
if not bucket then
return false, 'Feedback bucket could not be opened'
return false, 'Bucket extension is not available'
end
end


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


if not success then
if not success then
Line 237: Line 456:


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


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


local args =
local args =
Line 252: Line 471:


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


if title
if title
Line 260: Line 477:
and not title:find('^Template:')
and not title:find('^Template:')
then
then
pageTitle = title
pageTitle = title
end
end
end
end
Line 274: Line 489:


function p._main(args, pageTitle)
function p._main(args, pageTitle)
local feedback =
trim(args.feedback)


local feedback =
if feedback == '' then
args.feedback
feedback = 'No feedback provided'
or 'No feedback provided'
end


local user =
local user =
args.user
trim(args.user)
or 'Anonymous'


local id =
if user == '' then
args.id
user = 'Anonymous'
or ''
end
 
local resolvedBy =
args.resolvedby
or ''
 
local resolvedDate =
args.resolveddate
or ''


local resolved =
local resolved =
Line 304: Line 512:


local currentPage =
local currentPage =
pageTitle
trim(pageTitle)
or mw.title.getCurrentTitle().fullText
 
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)


--------------------------------------------------------
--------------------------------------------------------
Line 318: Line 548:
feedback = feedback,
feedback = feedback,
id = id,
id = id,
resolvedby = resolvedBy,
createddate = createddate,
resolveddate = resolvedDate
link = link,
image = image,
resolvedby = resolvedby,
resolveddate = resolveddate
})
})


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


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


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


--------------------------------------------------------
--------------------------------------------------------
Line 351: Line 586:
--------------------------------------------------------
--------------------------------------------------------


local categories = {}
local category


if resolved then
if resolved then
 
category =
table.insert(
categories,
'[[Category:Resolved Feedback]]'
'[[Category:Resolved Feedback]]'
)
else
else
 
category =
table.insert(
categories,
'[[Category:Unresolved Feedback]]'
'[[Category:Unresolved Feedback]]'
)
end
end


if mw.text.trim(
if id == '' then
tostring(id)
category =
) == '' then
category ..
 
table.insert(
categories,
'[[Category:Feedbacks Missing ID]]'
'[[Category:Feedbacks Missing ID]]'
)
end
end


return card
return card .. category
.. table.concat(categories)
end
end




------------------------------------------------------------
------------------------------------------------------------
-- Render feedback list
-- 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>'
Line 398: Line 618:


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


Line 417: Line 632:


------------------------------------------------------------
------------------------------------------------------------
-- View feedback
-- PAGE FEEDBACK
------------------------------------------------------------
------------------------------------------------------------


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


local parent =
local entries =
frame:getParent()
queryFeedback(page)


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


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


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


elseif parent
--------------------------------------------------------
and parent.args
-- Heading
and parent.args.status
--------------------------------------------------------
then


args =
root:tag('h2')
parent.args
:addClass('feedback-page-title')
:wikitext('Feedbacks')


elseif frame
--------------------------------------------------------
and frame.args
-- Filters
then
--------------------------------------------------------


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


elseif parent
local function addFilter(
and parent.args
label,
then
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


args =
addFilter(
parent.args
'Unresolved',
'unresolved',
true
)


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


args = {}
addFilter(
 
'All',
end
'all',
 
false
return p._view(
args,
frame
)
)
end


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


function p._view(args, frame)
local unresolvedList =
root:tag('div')
:addClass(
'feedback-page-list'
)
:addClass(
'feedback-page-list--unresolved'
)
:addClass(
'feedback-page-list--visible'
)


if not (mw.ext and mw.ext.bucket) then
if #unresolved == 0 then
return '<em>Bucket extension is not available.</em>'
unresolvedList:tag('div')
:addClass('feedback-empty')
:wikitext(
'No unresolved feedback.'
)
else
for _, entry in ipairs(unresolved) do
unresolvedList:wikitext(
renderCompactCard(entry)
)
end
end
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
-- Resolved
--------------------------------------------------------
--------------------------------------------------------


if filterStatus == 'resolved'
local resolvedList =
or filterStatus == 'unresolved'
root:tag('div')
then
:addClass(
'feedback-page-list'
)
:addClass(
'feedback-page-list--resolved'
)


local query =
if #resolved == 0 then
mw.ext.bucket('feedback')
resolvedList:tag('div')
 
:addClass('feedback-empty')
query.select(
:wikitext(
'page',
'No resolved feedback.'
'user',
)
'status',
else
'feedback',
for _, entry in ipairs(resolved) do
'id',
resolvedList:wikitext(
'resolvedby',
renderCompactCard(entry)
'resolveddate'
)
)
end
 
query.where(
'status',
filterStatus
)
 
return renderFeedbackList(
query.run()
)
end
end


Line 524: Line 787:
--------------------------------------------------------
--------------------------------------------------------


if filterStatus == 'all' then
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


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


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


return renderFeedbackList(
function p.counts(frame)
query.run()
local entries =
queryFeedback(
currentSubjectPage()
)
)
end


--------------------------------------------------------
local unresolved = 0
-- Default tabbed view
local resolved = 0
--------------------------------------------------------


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


qAll.select(
return tostring(
'page',
mw.html.create('span')
'user',
:addClass('feedback-count-data')
'status',
:attr(
'feedback',
'data-unresolved',
'id',
unresolved
'resolvedby',
)
'resolveddate'
:attr(
'data-resolved',
resolved
)
)
)
end


local allEntries =
qAll.run()


local allList = {}
------------------------------------------------------------
local unresolvedList = {}
-- TABLE / DASHBOARD
local resolvedList = {}
------------------------------------------------------------


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


for _, row in ipairs(allEntries) do
local table =
mw.html.create('table')
:addClass('feedback-table')


local card =
local head =
renderCard(
table:tag('thead')
row,
:tag('tr')
false
)


table.insert(
head:tag('th')
allList,
:wikitext('Status')
card
)


if row.status == 'resolved' then
head:tag('th')
:wikitext('Page')


table.insert(
head:tag('th')
resolvedList,
:wikitext('Feedback')
card
)


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


table.insert(
head:tag('th')
unresolvedList,
:wikitext('Date')
card
)


end
local body =
end
table:tag('tbody')
end


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


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


if #allList > 0 then
row:tag('td')
:addClass(
resolved
and 'feedback-table-status--resolved'
or 'feedback-table-status--unresolved'
)
:wikitext(
resolved
and 'Resolved'
or 'Unresolved'
)


allContent =
row:tag('td')
table.concat(
:addClass('feedback-table-page')
allList,
:wikitext(
'\n'
entry.page or ''
)
)


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


allContent =
row:tag('td')
'<em>No feedback entries found.</em>'
:wikitext(
formatUser(entry.user)
)


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


--------------------------------------------------------
return tostring(table)
-- Unresolved content
end
--------------------------------------------------------
 
 
function p.view(frame)
local parent =
frame:getParent()
 
local args
 
if frame
and frame.args
and frame.args.status
then
args = frame.args


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


if #unresolvedList > 0 then
elseif frame
and frame.args
then
args = frame.args


unresolvedContent =
elseif parent
table.concat(
and parent.args
unresolvedList,
then
'\n'
args = parent.args
)


else
else
args = {}
end
return p._view(
args,
frame
)
end


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


function p._view(args, frame)
local bucket =
getBucket()
if not bucket then
return
'<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


--------------------------------------------------------
--------------------------------------------------------
-- Resolved content
-- Query
--------------------------------------------------------
--------------------------------------------------------


local resolvedContent
local query =
bucket


if #resolvedList > 0 then
query.select(
'page',
'user',
'status',
'feedback',
'id',
'createddate',
'link',
'image',
'resolvedby',
'resolveddate'
)


resolvedContent =
if filterStatus == 'resolved'
table.concat(
or filterStatus == 'unresolved'
resolvedList,
then
'\n'
query.where(
)
'status',
filterStatus
)
end


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


resolvedContent =
if not success or not entries then
'<em>No resolved feedback entries found.</em>'
return
'<em>Could not load feedback.</em>'
end


end
entries =
sortNewestFirst(entries)


--------------------------------------------------------
--------------------------------------------------------
-- Tabber
-- Specific status
--------------------------------------------------------
--------------------------------------------------------


local tabberBody =
if filterStatus == 'resolved'
string.format(
or filterStatus == 'unresolved'
'All=\n%s\n|-|Unresolved=\n%s\n|-|Resolved=\n%s',
then
allContent,
return renderFeedbackList(
unresolvedContent,
entries
resolvedContent
)
)
end
--------------------------------------------------------
-- All
--------------------------------------------------------


return frame:callParserFunction(
return renderTable(entries)
'#tag',
{
'tabber',
tabberBody
}
)
end
end
p.table = p.view


return p
return p