Jump to content

Editing Module:Feedback

Warning: You are not logged in. Once you make an edit, a temporary account will be created for you. Learn more. Log in or create an account to continue receiving notifications after this account expires, and to access other features.
The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
Latest revision Your text
Line 2: Line 2:


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


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


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


return value == 'true'
return v ~= ''
or value == 'yes'
and v ~= '0'
or value == '1'
and v ~= 'false'
or value == 'resolved'
and v ~= 'no'
end
and v ~= 'off'
 
 
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 = trim(user)
user = mw.text.trim(tostring(user or ''))


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




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


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


local link = trim(entry.link)
local resolved = (entry.status == 'resolved')


if link ~= '' then
local user = entry.user or 'Anonymous'
local linkWrapper =
local feedback = entry.feedback or 'No feedback provided'
wrapper:tag('span')
local page = entry.page
:addClass('feedback-attachment-link')
linkWrapper:tag('span')
:addClass('feedback-attachment-icon')
linkWrapper:wikitext(
'[' .. link .. ' ' .. mw.text.nowiki(link) .. ']'
)
end


local image = trim(entry.image)
local root = mw.html.create('div')
 
:addClass('feedback-box')
if image ~= '' then
:addClass(
image = mw.ustring.gsub(
resolved
image,
and 'feedback-box--resolved'
'^File:',
or 'feedback-box--unresolved'
''
)
)


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


if title then
--------------------------------------------------------
local imageWrapper =
-- Metadata
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
local meta = root:tag('div')
end
:addClass('feedback-meta')




------------------------------------------------------------
if page and page ~= '' then
-- RESOLUTION
------------------------------------------------------------


local function renderResolution(parent, entry)
meta:tag('span')
local resolvedBy = trim(entry.resolvedby)
:addClass('feedback-page')
local resolvedDate = trim(entry.resolveddate)
:wikitext(
 
string.format('[[%s]]', page)
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 =
meta:tag('span')
mw.html.create('div')
:addClass('feedback-separator')
:addClass('feedback-box')
:wikitext(' • ')
:addClass(
resolved
and 'feedback-box--resolved'
or 'feedback-box--unresolved'
)


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


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


local content =
meta:tag('span')
root:tag('div')
:addClass('feedback-user')
:addClass('feedback-content')
:wikitext(formatUser(user))


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


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


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


main:tag('span')
meta:tag('span')
:addClass('feedback-status')
:addClass('feedback-status')
:addClass(
:addClass(
Line 329: Line 101:
or 'Unresolved'
or 'Unresolved'
)
)


--------------------------------------------------------
--------------------------------------------------------
Line 334: Line 107:
--------------------------------------------------------
--------------------------------------------------------


main:tag('span')
root:tag('div')
:addClass('feedback-text')
:addClass('feedback-body')
:wikitext(
:wikitext(feedback)
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
-- Resolution instruction
--------------------------------------------------------
--------------------------------------------------------
content:node(
    renderAttachments(entry)
)


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


renderResolution(
root:tag('div')
content,
:addClass('feedback-instruction')
entry
)
 
--------------------------------------------------------
-- Missing ID warning
--------------------------------------------------------
 
if id == '' then
content:tag('div')
:addClass('feedback-missing-id')
:wikitext(
:wikitext(
'This feedback entry is missing an ID.'
'If this feedback has been resolved, edit this page '
.. 'and set <code>|resolved=true</code>.'
)
)
end
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)
return tostring(root)
Line 413: Line 133:


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


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


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


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


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


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


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


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


return true
return true
Line 456: Line 172:


------------------------------------------------------------
------------------------------------------------------------
-- MAIN
-- Main
--
--
-- Used by Template:Feedback
-- Used by Template:Feedback
--
-- Example:
--
-- {{Feedback
--  |feedback=Something could be improved here.
--  |user=Rei
--  |resolved=false
-- }}
------------------------------------------------------------
------------------------------------------------------------


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


Line 467: Line 192:
parent and parent.args
parent and parent.args
or frame.args
or frame.args
--------------------------------------------------------
-- Get page title
--------------------------------------------------------


local pageTitle = nil
local pageTitle = nil


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


Line 479: Line 210:
pageTitle = title
pageTitle = title
end
end
end
end


return p._main(
 
args,
return p._main(args, pageTitle)
pageTitle
 
)
end
end




function p._main(args, pageTitle)
function p._main(args, pageTitle)
local feedback =
local feedback =
trim(args.feedback)
args.feedback
or 'No feedback provided'


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


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


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


local resolved =
local resolved =
isTruthy(args.resolved)
isTruthy(args.resolved)


local status =
local status =
Line 510: Line 239:
and 'resolved'
and 'resolved'
or 'unresolved'
or 'unresolved'


local currentPage =
local currentPage =
trim(pageTitle)
pageTitle
or mw.title.getCurrentTitle().fullText
 


if currentPage == '' then
--------------------------------------------------------
currentPage =
-- Save to Bucket
mw.title.getCurrentTitle().fullText
--------------------------------------------------------
end


local id =
local saved, saveError = saveFeedback({
trim(args.id)
page = currentPage,
user = user,
status = status,
feedback = feedback
})


local createddate =
trim(args.createddate)


local link =
if not saved then
trim(args.link)


local image =
return '<strong style="color:red;">Feedback bucket error: '
trim(args.image)
.. mw.text.nowiki(saveError or 'Unknown error')
.. '</strong>'


local resolvedby =
end
trim(args.resolvedby)


local resolveddate =
trim(args.resolveddate)


--------------------------------------------------------
--------------------------------------------------------
-- Save to Bucket
-- Render feedback card
--------------------------------------------------------
--------------------------------------------------------


local saved, saveError =
local card = renderCard({
saveFeedback({
 
page = currentPage,
page = nil,
user = user,
 
status = status,
user = user,
feedback = feedback,
 
id = id,
status = status,
createddate = createddate,
link = link,
image = image,
resolvedby = resolvedby,
resolveddate = resolveddate
})


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


--------------------------------------------------------
}, true)
-- 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
-- Categorize feedback
--------------------------------------------------------
--------------------------------------------------------


Line 589: Line 291:


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


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


return card .. category
return card .. category
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>'
end
end


local list = {}
local list = {}


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


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




------------------------------------------------------------
------------------------------------------------------------
-- PAGE FEEDBACK
-- View feedback
--
-- Examples:
--
-- {{#invoke:Feedback|view}}
--
-- {{#invoke:Feedback|view|status=all}}
--
-- {{#invoke:Feedback|view|status=resolved}}
--
-- {{#invoke:Feedback|view|status=unresolved}}
------------------------------------------------------------
------------------------------------------------------------


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


local entries =
local args
queryFeedback(page)
 
if frame
and frame.args
and frame.args.status
then


local unresolved = {}
args = frame.args
local resolved = {}
 
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 = {}


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


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


--------------------------------------------------------
return p._view(args, frame)
-- Heading
 
--------------------------------------------------------
end
 


root:tag('h2')
function p._view(args, frame)
:addClass('feedback-page-title')
:wikitext('Feedbacks')


--------------------------------------------------------
--------------------------------------------------------
-- Filters
-- Check Bucket
--------------------------------------------------------
--------------------------------------------------------


local filters =
if not (mw.ext and mw.ext.bucket) then
root:tag('div')
 
:addClass('feedback-filters')
return
'<em>Bucket extension is not available.</em>'


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
end


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


addFilter(
frame = frame or mw.getCurrentFrame()
'Resolved',
'resolved',
false
)


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


--------------------------------------------------------
--------------------------------------------------------
-- Unresolved
-- Determine requested status
--------------------------------------------------------
--------------------------------------------------------


local unresolvedList =
local rawStatus =
root:tag('div')
args.status
:addClass(
or args[1]
'feedback-page-list'
 
)
 
:addClass(
local filterStatus =
'feedback-page-list--unresolved'
rawStatus
)
and mw.text.trim(rawStatus):lower()
:addClass(
or nil
'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
-- Specific status
--------------------------------------------------------
--------------------------------------------------------


local resolvedList =
if filterStatus == 'resolved'
root:tag('div')
or filterStatus == 'unresolved'
:addClass(
then
'feedback-page-list'
 
)
local query =
:addClass(
mw.ext.bucket('feedback')
'feedback-page-list--resolved'
 
)
 
query.select(
'page',
'user',
'status',
'feedback'
)
 
 
query.where(
'status',
filterStatus
)
 
 
return renderFeedbackList(
query.run()
)


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
end


--------------------------------------------------------
--------------------------------------------------------
-- All
-- All feedback
--------------------------------------------------------
--------------------------------------------------------


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


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


return tostring(root)
end


query.select(
'page',
'user',
'status',
'feedback'
)


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


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


local unresolved = 0
end
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')
-- Tabber mode
:addClass('feedback-count-data')
--
:attr(
-- Default:
'data-unresolved',
--
unresolved
-- All
)
-- Unresolved
:attr(
-- Resolved
'data-resolved',
--------------------------------------------------------
resolved
)
)
end


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


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


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


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


local head =
local allEntries =
table:tag('thead')
qAll.run()
:tag('tr')


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


head:tag('th')
local allList = {}
:wikitext('Page')
local unresolvedList = {}
local resolvedList = {}


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


head:tag('th')
--------------------------------------------------------
:wikitext('User')
-- Split entries by status
--------------------------------------------------------


head:tag('th')
if allEntries and #allEntries > 0 then
:wikitext('Date')


local body =
for _, row in ipairs(allEntries) do
table:tag('tbody')


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


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


row:tag('td')
table.insert(
:addClass(
allList,
resolved
card
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')
if row.status == 'resolved' then
:addClass('feedback-table-text')
 
:wikitext(
table.insert(
entry.feedback or ''
resolvedList,
)
card
)


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


row:tag('td')
table.insert(
:wikitext(
unresolvedList,
entry.createddate or ''
card
)
)
end


return tostring(table)
end
end


end


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


local args


if frame
--------------------------------------------------------
and frame.args
-- Build tab contents
and frame.args.status
--------------------------------------------------------
then
args = frame.args


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


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


elseif parent
allContent =
and parent.args
table.concat(allList, '\n')
then
args = parent.args


else
else
args = {}
 
allContent =
'<em>No feedback entries found.</em>'
 
end
end


return p._view(
args,
frame
)
end


local unresolvedContent
if #unresolvedList > 0 then
unresolvedContent =
table.concat(unresolvedList, '\n')
else


function p._view(args, frame)
unresolvedContent =
local bucket =
'<em>No unresolved feedback entries found.</em>'
getBucket()


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


local rawStatus =
args.status
or args[1]


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


--------------------------------------------------------
if #resolvedList > 0 then
-- Query
--------------------------------------------------------


local query =
resolvedContent =
bucket
table.concat(resolvedList, '\n')


query.select(
else
'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 =
resolvedContent =
pcall(function()
'<em>No resolved feedback entries found.</em>'
return query.run()
end)


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


entries =
sortNewestFirst(entries)


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


if filterStatus == 'resolved'
local tabberBody = 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(
-- All
'#tag',
--------------------------------------------------------
{
'tabber',
tabberBody
}
)


return renderTable(entries)
end
end


p.table = p.view


return p
return p
Please note that all contributions to The Petit Planet Wiki are considered to be released under the Creative Commons Attribution-ShareAlike (see Petit Planet Wiki:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!
Cancel Editing help (opens in new window)
Preview page with this template
Below are commonly used wikitext from MediaWiki:Edittools. Click on any to insert them in the edit box.
File Pages==Summary====Licensing=={{Fairuse}}{{File|}}
Dynamic Page List¦²«»²{¦}²«»«/»«!----»
Array in DPL¹^2{}^2^2{¹}^2
Parser Functions{{#if:||}}
{{#ifeq:|||}}
{{#expr:}}
{{#switch:||#default=}}
{{#replace:||}}
{{#titleparts:}}
{{#tag:|}}
Markup{{}}|[][[]][[Category:]]#REDIRECT [[]] [[Category:Redirect Pages]]<code></code><includeonly></includeonly><noinclude></noinclude><nowiki></nowiki><!----><br>§
Notices{{Stub|}}{{Stub Dialogue}}{{Upcoming}}{{Under Construction}}{{Placeholder|}}
Magic Words{{!}}{{formatnum:}}{{lc:}}{{uc:}}{{PAGENAME}}{{FULLPAGENAME}}{{ROOTPAGENAME}}{{BASEPAGENAME}}{{SUBPAGENAME}}{{DISPLAYTITLE:|noreplace}}__TOC____NOTOC____EXPECTUNUSEDCATEGORY____HIDDENCAT__
HTML Entities&mdash;&nbsp;&comma;&shy;