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
 
(14 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 resolved = (entry.status == 'resolved')
local link = trim(entry.link)


local user =
if link ~= '' then
entry.user
local linkWrapper =
or 'Anonymous'
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)


local feedback =
if resolvedBy == '' and resolvedDate == '' then
entry.feedback
return
or 'No feedback provided'
end


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


local id =
resolution:wikitext('Resolved by ')
mw.text.trim(
tostring(entry.id or '')
)


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


local resolvedDate =
if resolvedDate ~= '' then
mw.text.trim(
resolution:wikitext(
tostring(entry.resolveddate or '')
' on ' .. mw.text.nowiki(resolvedDate)
)
)
end
end


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


--------------------------------------------------------
------------------------------------------------------------
-- Meta
-- RESOLVE CONTROL
--------------------------------------------------------
------------------------------------------------------------


local meta = root:tag('div')
local function renderResolveControl(
:addClass('feedback-meta')
parent,
id,
page,
compact
)
if trim(id) == '' then
return
end


if page and page ~= '' then
local wrapper =
parent:tag('div')
:addClass('feedback-resolve')


meta:tag('span')
local control =
:addClass('feedback-page')
wrapper:tag('span')
:wikitext(
:addClass('feedback-resolve-button')
string.format(
:attr(
'[[%s]]',
'data-feedback-id',
page
id
)
)
:attr(
'data-feedback-page',
page or ''
)
:attr(
'role',
'button'
)
:attr(
'tabindex',
'0'
)
:attr(
'aria-label',
'Resolve feedback'
)
)


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


meta:tag('span')
------------------------------------------------------------
:addClass('feedback-user')
-- FULL FEEDBACK CARD
:wikitext(
------------------------------------------------------------
formatUser(user)
 
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


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


meta:tag('span')
main:tag('span')
:addClass('feedback-status')
:addClass('feedback-status')
:addClass(
:addClass(
Line 124: Line 331:


--------------------------------------------------------
--------------------------------------------------------
-- Feedback
-- 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')
.. '[#feedback-resolve-'
:addClass('feedback-missing-id')
.. id
:wikitext(
.. ' Resolve]'
'This feedback entry is missing an ID.'
)
)
end


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


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


Line 189: 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 = pcall(function()
local success, err = pcall(function()
bucket.put({
bucket.put({
page = data.page,
page = data.page,
Line 213: Line 439:
feedback = data.feedback,
feedback = data.feedback,
id = data.id,
id = data.id,
createddate = data.createddate,
link = data.link,
image = data.image,
resolvedby = data.resolvedby,
resolvedby = data.resolvedby,
resolveddate = data.resolveddate
resolveddate = data.resolveddate
})
})
end)
end)


Line 228: 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 243: 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 251: Line 477:
and not title:find('^Template:')
and not title:find('^Template:')
then
then
pageTitle = title
pageTitle = title
end
end
end
end
Line 265: 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 295: 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 309: 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 342: 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 389: 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 408: 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',
args = {}
'resolved',
false
)


end
addFilter(
 
'All',
return p._view(
'all',
args,
false
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 515: Line 787:
--------------------------------------------------------
--------------------------------------------------------


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


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


query.select(
return tostring(root)
'page',
end
'user',
 
'status',
 
'feedback',
------------------------------------------------------------
'id',
-- COUNTS
'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


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


return p
return p