Module:Documentation: Difference between revisions

From Coral Island Wiki
Jump to navigation Jump to search
(Created page with "-- <pre> -------------------------------------------------------------------------------- -- This module implements {{T|Documentation}}. -- -- @module documentation -- @alias p -- @release stable -- @require Dev:Arguments -- @require Dev:Config -- @require Dev:I18n -- @require Dev:Languages -- @require [[Global Lua Modules/Message box|Dev:Message bo...")
 
(copy genshin impact wiki's iteration)
Line 1: Line 1:
-- <pre>
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Copied from Genshin Impact Wiki.
-- This module implements {{T|Documentation}}.
-- This module implements {{T|Documentation}}.
--
--
Line 9: Line 9:
-- @require [[Global Lua Modules/Config|Dev:Config]]
-- @require [[Global Lua Modules/Config|Dev:Config]]
-- @require [[Global Lua Modules/I18n|Dev:I18n]]
-- @require [[Global Lua Modules/I18n|Dev:I18n]]
-- @require [[Global Lua Modules/Languages|Dev:Languages]]
-- @require [[Global Lua Modules/Loanguages|Dev:Languages]]
-- @require [[Global Lua Modules/Message box|Dev:Message box]]
-- @require [[Global Lua Modules/Message box|Dev:Message box]]
-- @require [[Global Lua Modules/Yesno|Dev:Yesno]]
-- @require [[Global Lua Modules/Yesno|Dev:Yesno]]
Line 15: Line 15:
-- @require Module:Documentation/i18n
-- @require Module:Documentation/i18n
-- @require [[MediaWiki:Module:Documentation.css]]
-- @require [[MediaWiki:Module:Documentation.css]]
-- @author [[User:FishTank]]
-- @author [[User:ExE Boss]]
-- @attribution [[wikipedia:Module:Documentation|Module:Documentation]] (Wikipedia)
-- @attribution [[wikipedia:Module:Documentation|Module:Documentation]] (Wikipedia)
-- @see [[wikipedia:Module:Documentation|Original module on Wikipedia]]
-- @see [[wikipedia:Module:Documentation|Original module on Wikipedia]]
Line 24: Line 22:
local libraryUtil = require('libraryUtil');
local libraryUtil = require('libraryUtil');
local checkType = libraryUtil.checkType;
local checkType = libraryUtil.checkType;
local lib = require('Module:Feature')


-- Get required modules.
-- Get required modules.
local getArgs = require('Dev:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs
local pageExists = require('Dev:Linkless Exists').pageExists
local checkExists = require('Module:Exists').checkExists
local messageBox = require('Dev:Message box')
local yesno = require('Module:Yesno')
local yesno = require('Dev:Yesno')


-- Get the config table.
-- Get the config table.
local cfg = require('Dev:Config').loadConfig('Documentation')
local cfg = require('Module:Config').loadConfig('Documentation')
local i18n = require('Dev:I18n').loadMessages('Documentation', 'Common')
local i18n = require('Module:I18n').loadMessages('Documentation')
local languages = require('Dev:Languages')
local languages = require('Module:Languages')


local p = {}
local p = {}
p.i18n = i18n
p.i18n = i18n


-- Often-used functions.
-- Capitalizes first letter of strings
local ugsub = mw.ustring.gsub
-- src: https://stackoverflow.com/questions/2421695/first-character-uppercase-lua
 
local function firstToUpper(str)
local IS_DEV_WIKI = mw.site.server == 'https://dev.fandom.com';
    return (str:gsub("^%l", string.upper))
end


-- --------------------------------------------------------------------------
-- --------------------------------------------------------------------------
Line 58: Line 57:
-- message are substituted with values from the table keys [1], [2] etc.
-- message are substituted with values from the table keys [1], [2] etc.
-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
-- message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
-- p.message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
--
--
-- @function p.message
-- @function p.message
Line 67: Line 66:
-- @return {string|number|boolean|table|nil}
-- @return {string|number|boolean|table|nil}
--]]
--]]
local function message(cfgKey, valArray, expectType)
function p.message(cfgKey, valArray, expectType)
local msg = cfg:getValue(cfgKey)
local msg = cfg:getValue(cfgKey)
expectType = expectType or 'string'
expectType = expectType or 'string'
Line 77: Line 76:
end
end


local function getMessageVal(match)
function getMessageVal(match)
match = tonumber(match)
match = tonumber(match)
return valArray[match] or error('message: no value found for key $' .. match .. ' in message cfg.' .. cfgKey, 4)
return valArray[match] or error('message: no value found for key $' .. match .. ' in message cfg.' .. cfgKey, 4)
end
end


local ret = ugsub(msg, '$([1-9][0-9]*)', getMessageVal)
local ret = mw.ustring.gsub(msg, '$([1-9][0-9]*)', getMessageVal)
return ret
return ret
end
end


p.message = message
function p.makeWikilink(page, display)
 
local function makeWikilink(page, display)
if display then
if display then
return mw.ustring.format('[[%s|%s]]', page, display)
return mw.ustring.format('[[%s|%s]]', page, display)
Line 96: Line 93:
end
end


p.makeWikilink = makeWikilink
function p.makeUrlWikilink(page, display)
 
local function makeUrlWikilink(page, display)
-- This code prevents redlinks, but doesn't work for categories, and those
-- This code prevents redlinks, but doesn't work for categories, and those
-- are always blue-linked by the CategoryBlueLinks extension anyway.
-- are always blue-linked by the CategoryBlueLinks extension anyway.
Line 104: Line 99:
end
end


p.makeUrlWikilink = makeUrlWikilink


local function makeCategoryLink(cat, sort)
function p.makeCategoryLink(cat, sort)
local catns = mw.site.namespaces[14].name
local catns = mw.site.namespaces[14].name
return makeWikilink(catns .. ':' .. cat, sort)
return p.makeWikilink(catns .. ':' .. cat, sort)
end
end


p.makeCategoryLink = makeCategoryLink
function p.makeUrlLink(url, display)
 
local function makeUrlLink(url, display)
return mw.ustring.format('[%s %s]', url, display)
return mw.ustring.format('[%s %s]', url, display)
end
end


p.makeUrlLink = makeUrlLink
function p.makeToolbar(...)
 
local function makeToolbar(...)
local lim = select('#', ...)
local lim = select('#', ...)
if lim < 1 then
if lim < 1 then
Line 143: Line 133:
return not isFirst and ret:wikitext(')'):done() or nil
return not isFirst and ret:wikitext(')'):done() or nil
end
end
p.makeToolbar = makeToolbar


--[[
--[[
Line 152: Line 140:
-- @return {string}
-- @return {string}
--]]
--]]
local function resolveNamespace(subjectSpace)
function p.resolveNamespace(subjectSpace, upperPlural)
if subjectSpace == 10 then -- Template namespace
if subjectSpace == 10 then -- Template namespace
return 'template'
if upperPlural then return 'Templates'
else return 'template' end
elseif subjectSpace == 828 then -- Module namespace
elseif subjectSpace == 828 then -- Module namespace
return 'module'
if upperPlural then return 'Modules'
else return 'module' end
elseif subjectSpace == 6 then -- File namespace
elseif subjectSpace == 6 then -- File namespace
return 'file'
if upperPlural then return 'Files'
else return 'file' end
end
end
return 'other'
return 'other'
end
end
p.resolveNamespace = resolveNamespace;


-- --------------------------------------------------------------------------
-- --------------------------------------------------------------------------
Line 169: Line 158:
-- --------------------------------------------------------------------------
-- --------------------------------------------------------------------------


local function makeInvokeFunc(funcName)
function p.makeInvokeFunc(funcName)
return function (frame)
return function (frame)
local args = getArgs(frame, {
local args = getArgs(frame, {
Line 204: Line 193:
-- @return {string}
-- @return {string}
--]]
--]]
p.main = makeInvokeFunc('_main')
p.main = p.makeInvokeFunc('_main')


function p._main(args)
function p._main(args)
local env = p.getEnvironment(args)
local env = p.getEnvironment(args)
local root = mw.html.create()
if env.title == env.testcasesTitle then
root
if env.title.namespace == 828 then
:wikitext(p.protectionTemplate(env))
-- we are in module namespace, the testcase title is equal, so expand the testcase header
:wikitext(p.sandboxNotice(args, env))
local root = mw.html.create()
:tag('div')
root:wikitext(mw.getCurrentFrame():expandTemplate{title = 'Documentation/Testcase Header'})
:attr('id', message('main-div-id'))
return root
:addClass(message('main-div-classes'))
end
:newline()
else
:node(p._startBox(args, env))
local root = mw.html.create()
:node(p._content(args, env))
root
:node(p._endBox(args, env))
:wikitext(p.protectionTemplate(env))
:done()
:wikitext(p.sandboxNotice(args, env))
:wikitext(p.addTrackingCategories(env))
:tag('div')
return root
:attr('id', p.message('main-div-id'))
:addClass(p.message('main-div-classes'))
:newline()
:node(p._startBox(args, env))
:node(p._navigation(args, env))
:node(p._content(args, env))
:node(p._endBox(args, env))
:done()
:wikitext(p.addTrackingCategories(env))
:wikitext(p.addTypeCategory(args))
return root
end
end
end


Line 239: Line 239:
--- @property {Title|nil} Environment.templateTitle - the template (or module, file, etc.)
--- @property {Title|nil} Environment.templateTitle - the template (or module, file, etc.)
--- @property {Title|nil} Environment.docTitle - the /doc subpage.
--- @property {Title|nil} Environment.docTitle - the /doc subpage.
--- @property {Title|nil} Environment.docTitleEn - the /doc/en subpage.
--- @property {Title|nil} Environment.sandboxTitle - the /sandbox subpage.
--- @property {Title|nil} Environment.sandboxTitle - the /sandbox subpage.
--- @property {Title|nil} Environment.testcasesTitle - the /testcases subpage.
--- @property {Title|nil} Environment.testcasesTitle - the /testcases subpage.
Line 300: Line 299:
local title = env.title
local title = env.title
local subpage = title.subpageText
local subpage = title.subpageText
if subpage == message('sandbox-subpage') or subpage == message('testcases-subpage') then
if subpage == p.message('sandbox-subpage') or subpage == p.message('testcases-subpage') or subpage == p.message('doc-subpage') then
return mw.title.makeTitle(subjectSpace, title.baseText)
return mw.title.makeTitle(subjectSpace, title.baseText)
else
else
Line 316: Line 315:
docpage = docname
docpage = docname
else
else
docpage = env.docpageBase .. '/' .. message('doc-subpage')
docpage = env.docpageBase .. '/' .. p.message('doc-subpage')
end
end
return mw.title.new(docpage)
return mw.title.new(docpage)
end
-- Title object of the /doc/en subpage.
function envFuncs.docTitleEn()
local docTitle = env.docTitle
return mw.title.new(docTitle.text .. '/en', docTitle.nsText)
end
end


Line 340: Line 333:
local hasSubpages = env.hasSubpages
local hasSubpages = env.hasSubpages
local docTitle = env.docTitle
local docTitle = env.docTitle
local docTitleEn = env.docTitleEn
local forceI18n = yesno(args.i18n)
if (forceI18n or hasSubpages) and (pageExists(docTitleEn.prefixedText) or not pageExists(docTitle.prefixedText)) then
return docTitleEn
end
return docTitle
return docTitle
end
end
Line 363: Line 351:
function envFuncs.sandboxTitle()
function envFuncs.sandboxTitle()
    -- return nil
    -- return nil
return mw.title.new(env.docpageBase .. '/' .. message('sandbox-subpage'))
return mw.title.new(env.docpageBase .. '/' .. p.message('sandbox-subpage'))
end
end


Line 371: Line 359:
function envFuncs.testcasesTitle()
function envFuncs.testcasesTitle()
    -- return nil
    -- return nil
return mw.title.new(env.docpageBase .. '/' .. message('testcases-subpage'))
return mw.title.new(env.docpageBase .. '/' .. p.message('testcases-subpage'))
end
end


Line 378: Line 366:
-- : 'print-subpage' --> 'Print'
-- : 'print-subpage' --> 'Print'
function envFuncs.printTitle()
function envFuncs.printTitle()
if message('print-show') then
if p.message('print-show') then
return env.templateTitle:subPageTitle(message('print-subpage'))
return env.templateTitle:subPageTitle(p.message('print-subpage'))
end
end
end
end
Line 419: Line 407:
local templateTitle = env.templateTitle
local templateTitle = env.templateTitle
local sandboxTitle = env.sandboxTitle
local sandboxTitle = env.sandboxTitle
if pageExists(templateTitle.prefixedText) and pageExists(sandboxTitle.prefixedText) then
if checkExists(templateTitle.prefixedText) and checkExists(sandboxTitle.prefixedText) then
local compareUrl = mw.uri.fullUrl(
local compareUrl = mw.uri.fullUrl(
'Special:ComparePages',
'Special:ComparePages',
Line 433: Line 421:
local docTitle = env.docTitle
local docTitle = env.docTitle
if not args.content then
if not args.content then
if not pageExists(docTitle.prefixedText) and not env.hasSubpages then
if not checkExists(docTitle.prefixedText) and not env.hasSubpages then
return 'nodoc'
return 'nodoc'
elseif not pageExists(docTitle.prefixedText) and not pageExists(env.docTitleEn.prefixedText) and env.hasSubpages then
elseif not checkExists(docTitle.prefixedText) and env.hasSubpages then
return 'baddoc'
return 'baddoc'
end
end
Line 444: Line 432:
local docStatus = env.docStatus
local docStatus = env.docStatus
if not docStatus then
if not docStatus then
return message('documentation-icon')
return p.message('documentation-icon')
end
return p.message('documentation-icon-' .. docStatus)
end
function envFuncs.title()
-- The title object for the current page, or a test page passed with args.page.
local title
local titleArg = args.page
if titleArg then
title = mw.title.new(titleArg)
else
title = mw.title.getCurrentTitle()
end
end
return message('documentation-icon-' .. docStatus)
return title
end
end
return env
return env
end
end
Line 477: Line 475:
-- @return {string|nil}
-- @return {string|nil}
function p.sandboxNotice(args, env)
function p.sandboxNotice(args, env)
if not message('sandbox-notice-show', nil, 'boolean') then
if not p.message('sandbox-notice-show', nil, 'boolean') then
return nil
return nil
end
end
Line 490: Line 488:
-- Build the table of arguments to pass to {{T|ombox}}. We need just two fields, "image" and "text".
-- Build the table of arguments to pass to {{T|ombox}}. We need just two fields, "image" and "text".
local omargs = {}
local omargs = {}
omargs.image = message('sandbox-notice-image')
omargs.image = p.message('sandbox-notice-image')
-- Get the text. We start with the opening blurb, which is something like
-- Get the text. We start with the opening blurb, which is something like
-- "This is the template sandbox for [[Template:Foo]] (diff)."
-- "This is the template sandbox for [[Template:Foo]] (diff)."
local header = ''
local text = ''
local text = ''
local frame = mw.getCurrentFrame()
local frame = mw.getCurrentFrame()
Line 498: Line 497:
local pagetype
local pagetype
if subjectSpace == 10 then
if subjectSpace == 10 then
pagetype = message('sandbox-notice-pagetype-template')
pagetype = p.message('sandbox-notice-pagetype-template')
elseif subjectSpace == 828 then
elseif subjectSpace == 828 then
pagetype = message('sandbox-notice-pagetype-module')
pagetype = p.message('sandbox-notice-pagetype-module')
else
else
pagetype = message('sandbox-notice-pagetype-other')
pagetype = p.message('sandbox-notice-pagetype-other')
end
end
local templateLink = makeUrlWikilink(templateTitle.prefixedText)
local templateLink = p.makeUrlWikilink(templateTitle.prefixedText)
local compareUrl = env.compareUrl
local compareUrl = env.compareUrl
if isPreviewing or not compareUrl then
if isPreviewing or not compareUrl then
text = text .. message('sandbox-notice-blurb', {pagetype, templateLink})
header = header .. p.message('sandbox-notice-blurb', {pagetype, templateLink})
else
else
local compareDisplay = message('sandbox-notice-compare-link-display')
local compareDisplay = p.message('sandbox-notice-compare-link-display')
local compareLink = makeUrlLink(compareUrl, compareDisplay)
local compareLink = p.makeUrlLink(compareUrl, compareDisplay)
text = text .. message('sandbox-notice-diff-blurb', {pagetype, templateLink, compareLink})
header = header .. p.message('sandbox-notice-diff-blurb', {pagetype, templateLink, compareLink})
end
end
-- Get the test cases page blurb if the page exists. This is something like
-- Get the test cases page blurb if the page exists. This is something like
-- "See also the companion subpage for [[Template:Foo/testcases|test cases]]."
-- "See also the companion subpage for [[Template:Foo/testcases|test cases]]."
local testcasesTitle = env.testcasesTitle
local testcasesTitle = env.testcasesTitle
if testcasesTitle and pageExists(testcasesTitle.prefixedText) then
if testcasesTitle and checkExists(testcasesTitle.prefixedText) then
if testcasesTitle.namespace == mw.site.namespaces.Module.id then
if testcasesTitle.namespace == mw.site.namespaces.Module.id then
local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
local testcasesLinkDisplay = p.message('sandbox-notice-testcases-link-display')
local testcasesRunLinkDisplay = message('sandbox-notice-testcases-run-link-display')
local testcasesRunLinkDisplay = p.message('sandbox-notice-testcases-run-link-display')
local testcasesLink = makeUrlWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
local testcasesLink = p.makeUrlWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
local testcasesRunLink = makeUrlWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
local runTitle = testcasesTitle.talkPageTitle
text = text .. '<br />' .. message('sandbox-notice-testcases-run-blurb', {testcasesLink, testcasesRunLink})
local testcasesRunLink
if checkExists(runTitle.prefixedText) then
testcasesRunLink = p.makeUrlWikilink(runTitle.prefixedText, testcasesRunLinkDisplay)
else
testcasesRunLink = p.makeUrlLink(runTitle:fullUrl({
action = 'edit',
redlink = '1',
preload = p.message('module-testcases-run-preload'),
}), testcasesRunLinkDisplay)
end
local testcasesRunLink = p.makeUrlWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
text = text .. p.message('sandbox-notice-testcases-run-blurb', {testcasesLink, testcasesRunLink})
else
else
local testcasesLinkDisplay = message('sandbox-notice-testcases-link-display')
local testcasesLinkDisplay = p.message('sandbox-notice-testcases-link-display')
local testcasesLink = makeUrlWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
local testcasesLink = p.makeUrlWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
text = text .. '<br />' .. message('sandbox-notice-testcases-blurb', {testcasesLink})
text = text .. p.message('sandbox-notice-testcases-blurb', {testcasesLink})
end
end
end
end
-- Add the sandbox to the sandbox category.
-- Add the sandbox to the sandbox category.
text = text .. makeCategoryLink(message('sandbox-category'))
if title.namespace == 828 then
text = text .. p.makeCategoryLink(p.message('sandbox-module-category'))
else
text = text .. p.makeCategoryLink(p.message('sandbox-category'))
end
omargs.header = header
omargs.text = text
omargs.text = text
omargs.imagewidth = '50px'
omargs.class = 'sandbox'
local ret = '<div style="clear: both;"></div>'
local ret = '<div style="clear: both;"></div>'
ret = ret .. messageBox.main('ombox', omargs)
ret = ret .. frame:expandTemplate{ title = 'Mbox', args = omargs}
return ret
return ret
end
end
Line 568: Line 585:
-- The page is edit-protected.
-- The page is edit-protected.
mProtectionBanner = require('Dev:Protection banner')
mProtectionBanner = require('Dev:Protection banner')
local reason = message('protection-reason-edit')
local reason = p.message('protection-reason-edit')
return mProtectionBanner._main{reason, small = true}
return mProtectionBanner._main{reason, small = true}
elseif moveProt and moveProt ~= 'autoconfirmed' then
elseif moveProt and moveProt ~= 'autoconfirmed' then
Line 597: Line 614:
-- @return {string|nil}
-- @return {string|nil}
--]]
--]]
p.startBox = makeInvokeFunc('_startBox')
p.startBox = p.makeInvokeFunc('_startBox')


function p._startBox(args, env)
function p._startBox(args, env)
Line 655: Line 672:
if not preload then
if not preload then
if subjectSpace == 6 then -- File namespace
if subjectSpace == 6 then -- File namespace
preload = message('file-docpage-preload')
preload = p.message('file-docpage-preload')
elseif subjectSpace == 828 then -- Module namespace
elseif subjectSpace == 828 then -- Module namespace
preload = message('module-preload')
preload = p.message('module-preload')
else
else
preload = message('docpage-preload')
preload = p.message('docpage-preload')
end
end
end
end
Line 677: Line 694:
function p.renderStartBoxLinks(data)
function p.renderStartBoxLinks(data)


local function escapeBrackets(s)
function escapeBrackets(s)
-- Escapes square brackets with HTML entities.
-- Escapes square brackets with HTML entities.
s = s:gsub('%[', '&#91;') -- Replace square brackets with HTML entities.
s = s:gsub('%[', '&#91;') -- Replace square brackets with HTML entities.
Line 687: Line 704:
local docTitle = data.docTitle
local docTitle = data.docTitle
local title = data.title
local title = data.title
if pageExists(docTitle.prefixedText) then
if checkExists(docTitle.prefixedText) then
local viewLink = makeWikilink(docTitle.prefixedText, data.viewLinkDisplay)
local viewLink = p.makeWikilink(docTitle.prefixedText, data.viewLinkDisplay)
local editLink = makeUrlLink(docTitle:fullUrl({ action = 'edit' }, 'https'), data.editLinkDisplay)
local editLink = p.makeUrlLink(docTitle:fullUrl({ action = 'edit' }, 'https'), data.editLinkDisplay)
local historyLink = makeUrlLink(docTitle:fullUrl({ action = 'history' }, 'https'), data.historyLinkDisplay)
local historyLink = p.makeUrlLink(docTitle:fullUrl({ action = 'history' }, 'https'), data.historyLinkDisplay)
local purgeLink = makeUrlLink(title:fullUrl({ action = 'purge' }, 'https'), data.purgeLinkDisplay)
local purgeLink = p.makeUrlLink(title:fullUrl({ action = 'purge' }, 'https'), data.purgeLinkDisplay)
ret = '[%s] [%s] [%s] [%s]'
ret = '[%s] [%s] [%s] [%s]'
ret = escapeBrackets(ret)
ret = escapeBrackets(ret)
ret = mw.ustring.format(ret, viewLink, editLink, historyLink, purgeLink)
ret = mw.ustring.format(ret, viewLink, editLink, historyLink, purgeLink)
else
else
local createLink = makeUrlLink(docTitle:fullUrl({
local createLink = p.makeUrlLink(docTitle:fullUrl({
action = 'edit',
action = 'edit',
redlink = '1',
redlink = '1',
preload = data.preload,
preload = data.preload,
}, 'https'), data.createLinkDisplay)
}, 'https'), data.createLinkDisplay)
ret = '[%s]'
local purgeLink = p.makeUrlLink(title:fullUrl({ action = 'purge' }, 'https'), data.purgeLinkDisplay)
ret = '[%s] [%s]'
ret = escapeBrackets(ret)
ret = escapeBrackets(ret)
ret = mw.ustring.format(ret, createLink)
ret = mw.ustring.format(ret, createLink, purgeLink)
end
end
return ret
return ret
Line 744: Line 762:
data.heading = heading
data.heading = heading
elseif subjectSpace == 10 then -- Template namespace
elseif subjectSpace == 10 then -- Template namespace
data.heading = message('documentation-icon-wikitext', {env.docIcon}) .. ' ' .. i18n:msg('documentation-heading')
data.heading = p.message('documentation-icon-wikitext', {env.docIcon}) .. ' ' .. i18n:msg('documentation-heading')
data.subHeading = i18n:msg('documentation-visibility')
data.subHeading = i18n:msg('documentation-visibility')
elseif subjectSpace == 828 then -- Module namespace
elseif subjectSpace == 828 then -- Module namespace
data.heading = message('documentation-icon-wikitext', {env.docIcon}) .. ' ' .. i18n:msg('module-namespace-heading')
data.heading = p.message('documentation-icon-wikitext', {env.docIcon}) .. ' ' .. i18n:msg('module-namespace-heading')
elseif subjectSpace == 6 then -- File namespace
elseif subjectSpace == 6 then -- File namespace
data.heading = i18n:msg('file-namespace-heading')
data.heading = i18n:msg('file-namespace-heading')
Line 761: Line 779:
-- We are in the template or template talk namespaces.
-- We are in the template or template talk namespaces.
data.headingFontWeight = 'bold'
data.headingFontWeight = 'bold'
data.headingFontSize = '125%'
data.headingFontSize = '100%'
else
else
data.headingFontSize = '150%'
data.headingFontSize = '100%'
end
end


-- Data for the [view][edit][history][purge] or [create] links.
-- Data for the [view][edit][history][purge] or [create] links.
if links then
if links then
data.linksClass = message('start-box-linkclasses')
data.linksClass = p.message('start-box-linkclasses')
data.linksId = message('start-box-link-id')
data.linksId = p.message('start-box-link-id')
data.links = links
data.links = links
end
end
Line 790: Line 808:
local sbox = mw.html.create('div')
local sbox = mw.html.create('div')
sbox
sbox
:addClass(message('start-box-div-classes'))
:addClass(p.message('start-box-div-classes'))
:newline()
:newline()
:tag('span')
:tag('span')
Line 801: Line 819:
sbox
sbox
:tag('div')
:tag('div')
:css('font-size', data.headingFontSize)
:css('float', 'right')
:css('float', 'right')
:tag('span')
:tag('span')
Line 817: Line 836:
end
end
return sbox
return sbox
end
p.navigation = p.makeInvokeFunc('_navigation')
function p._navigation(args, env)
-- Get environment data.
env = env or p.getEnvironment(args)
local frame = mw.getCurrentFrame()
local nav = mw.html.create('table')
nav
:addClass('doctable')
:css('width', '100%')
local navhead = mw.html.create('tr')
:tag('th')
:addClass('doc-header')
local colspan = 0
if args.navHeader ~= nil then
navhead:wikitext(args.navHeader):done()
end
local lnkdta = mw.html.create('tr'):addClass('links')
local tags = {}
tags[1] = lnkdta:tag('td')
tags[1]:wikitext('[[' .. env.templateTitle.prefixedText .. '|Main]]')
colspan = colspan + 1
if checkExists(env.docTitle.prefixedText) then
tags[2] = lnkdta:tag('td')
tags[2]:wikitext('[[' .. env.docTitle.prefixedText .. '|' .. firstToUpper(p.message('doc-link-display')) .. ']]')
colspan = colspan + 1
end
if checkExists(env.sandboxTitle.prefixedText) then
tags[3] = lnkdta:tag('td')
tags[3]:wikitext('[[' .. env.sandboxTitle.prefixedText .. '|' .. firstToUpper(i18n:msg('sandbox-link-display')) .. ']]')
colspan = colspan + 1
end
if checkExists(env.testcasesTitle.prefixedText) then
tags[4] = lnkdta:tag('td')
tags[4]:wikitext('[[' .. env.testcasesTitle.prefixedText ..  '|' .. firstToUpper(i18n:msg('testcases-link-display')) .. ']]')
colspan = colspan + 1
end
tags[5] = lnkdta:tag('td')
tags[5]:wikitext('[' .. tostring(mw.uri.fullUrl('Special:WhatLinksHere', 'hidelinks=1&hideredirs=1&target=' .. env.templateTitle.prefixedText)) .. ' Usage]')
colspan = colspan + 1
for k,v in pairs(tags) do
v:css('width', (100 / colspan) .. '%')
end
navhead:attr('colspan', colspan)
if args.navHeader ~= nil then
nav:node(navhead)
end
nav:node(lnkdta)
nav:allDone()
return nav
end
end


Line 834: Line 905:
-- @return {string}
-- @return {string}
--]]
--]]
p.content = makeInvokeFunc('_content')
p.content = p.makeInvokeFunc('_content')


function p._content(args, env)
function p._content(args, env)
Line 841: Line 912:
local content = args.content
local content = args.content
local root = mw.html.create()
local root = mw.html.create()
local frame = mw.getCurrentFrame()
if not content and docTitle then
if not content and docTitle then
local subjectSpace = env.subjectSpace
local subjectSpace = env.subjectSpace
Line 846: Line 918:
if not preload then
if not preload then
if subjectSpace == 6 then -- File namespace
if subjectSpace == 6 then -- File namespace
preload = message('file-docpage-preload')
preload = p.message('file-docpage-preload')
elseif subjectSpace == 828 then -- Module namespace
elseif subjectSpace == 828 then -- Module namespace
preload = message('module-preload')
preload = p.message('module-preload')
else
else
preload = message('docpage-preload')
preload = p.message('docpage-preload')
end
end
end
end
Line 858: Line 930:


local docMissing = i18n:msg(
local docMissing = i18n:msg(
resolveNamespace(subjectSpace) .. '-documentation-missing',
p.resolveNamespace(subjectSpace) .. '-documentation-missing',
docTitleCreate:fullUrl({
docTitleCreate:fullUrl({
action = 'edit',
action = 'edit',
Line 866: Line 938:
)
)


if not pageExists(docTitle.prefixedText) and not hasSubpages then
if not checkExists(docTitle.prefixedText) and not hasSubpages then
content = docMissing
content = docMissing
else
else
Line 876: Line 948:
format = 'list',
format = 'list',
page = docTitle.prefixedText,
page = docTitle.prefixedText,
class = message('languages-list-div-classes'),
class = p.message('languages-list-div-classes'),
select = currentLang,
select = currentLang,
editintro = 'Template:Documentation/editintro',
editintro = 'Template:Documentation/editintro',
})
})
end
end
if not pcall(function()
content = args._content or tostring(languages.langs{
content = args._content or frame:expandTemplate{title = docTitle.prefixedText}
format = 'transclude',
end) then
page = docTitle.prefixedText,
content = '[[' .. docTitle.prefixedText .. ']]'
notice = 'none',
end
missing = (function()
    -- prefixedText here does not work since it does not leave a colon in front of the page name
    -- if the page is in main namespace, making expandTemplate assume we're invoking a template.
    local prefixedTitle = docTitle.nsText .. ':' .. docTitle.text
if pcall(function() return mw.getCurrentFrame():expandTemplate{title = prefixedTitle} end) then
return mw.getCurrentFrame():expandTemplate{title = prefixedTitle}
else
return docMissing
end
end)()
})
end
end
end
end
local cbox = root:tag('div')
local cbox = root:tag('div')
cbox
cbox
:addClass(message('content-box-div-classes'))
:addClass(p.message('content-box-div-classes'))
:css("display", "flow-root")
:css("display", "flow-root")
-- The line breaks are necessary so that "=== Headings ===" at the start
-- The line breaks are necessary so that "=== Headings ===" at the start
-- and end of docs are interpreted correctly.
-- and end of docs are interpreted correctly.
:wikitext(mw.getCurrentFrame():expandTemplate{title = message('template-tocright')})
:wikitext(mw.getCurrentFrame():expandTemplate{title = p.message('template-tocright')})
:newline()
:newline()
:wikitext(content or '')
:wikitext(content or '')
Line 920: Line 981:
-- @return {string}
-- @return {string}
--]]
--]]
p.contentTitle = makeInvokeFunc('_contentTitle')
p.contentTitle = p.makeInvokeFunc('_contentTitle')


function p._contentTitle(args, env)
function p._contentTitle(args, env)
env = env or p.getEnvironment(args)
env = env or p.getEnvironment(args)
local docTitle = env.docTitle
local docTitle = env.docTitle
if not args.content and docTitle and pageExists(docTitle.prefixedText) then
if not args.content and docTitle and checkExists(docTitle.prefixedText) then
return docTitle.prefixedText
return docTitle.prefixedText
else
else
Line 939: Line 1,000:
-- This function generates the end box (also known as the link box).
-- This function generates the end box (also known as the link box).
--
--
-- The HTML is generated by the <code>{{fmbox}}</code> template, courtesy of [[Module:Message box]].
-- The HTML is generated by the {{T|fmbox}} template, courtesy of [[Module:Message box]].
--
--
-- ; Messages:
-- ; Messages:
Line 950: Line 1,011:
-- @param[opt] {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @param[opt] {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string}
-- @return {string}
p.endBox = makeInvokeFunc('_endBox')
p.endBox = p.makeInvokeFunc('_endBox')


function p._endBox(args, env)
function p._endBox(args, env)
Line 960: Line 1,021:


local root = mw.html.create('div')
local root = mw.html.create('div')
:attr('id', message('end-box-div-id'))
:attr('id', p.message('end-box-div-id'))
:addClass(message('end-box-div-classes'))
:addClass(p.message('end-box-div-classes'))
:css('clear', 'both')
:css('clear', 'both')


local hasDocPage  = subjectSpace and docTitle and pageExists(docTitle.prefixedText)
local hasDocPage  = subjectSpace and docTitle and checkExists(docTitle.prefixedText)
local linkBoxSpace = subjectSpace == 2 or subjectSpace == 10 or subjectSpace == 828
local linkBoxSpace = subjectSpace == 2 or subjectSpace == 10 or subjectSpace == 828


Line 983: Line 1,044:
end
end


if IS_DEV_WIKI then
-- Hack to ensure that the style of the end box matches the existing style
root
--[[
:cssText('background-color:#EEE')
:css('background-color', 'var(--theme-page-background-color--secondary,#EEE)')
:css('border-color', 'var(--theme-border-color,#CCC)')
--]]
:css('font-size', '100%')
:css('margin', '0')
:css('padding', '.5em 1em')
end
local customLanguagesNotice = i18n:msg('custom-languages-notice', docTitle.prefixedText,
local customLanguagesNotice = i18n:msg('custom-languages-notice', docTitle.prefixedText,
docTitle:fullUrl({ action = 'edit' }, 'https'):gsub("%%", "%%%%"), 'bottom')
docTitle:fullUrl({ action = 'edit' }, 'https'):gsub("%%", "%%%%"), 'bottom')
Line 1,093: Line 1,142:
local testcasesLinks = mw.html.create()
local testcasesLinks = mw.html.create()


if pageExists(sandboxTitle.prefixedText) then
if checkExists(sandboxTitle.prefixedText) then
sandboxLinks:wikitext(
sandboxLinks:wikitext(
makeUrlWikilink(sandboxTitle.prefixedText, i18n:msg('sandbox-link-display')),
p.makeUrlWikilink(sandboxTitle.prefixedText, i18n:msg('sandbox-link-display')),
' '
' '
)
)


local sandboxEditLink = makeUrlLink(
local sandboxEditLink = p.makeUrlLink(
sandboxTitle:fullUrl({ action = 'edit' }, 'https'),
sandboxTitle:fullUrl({ action = 'edit' }, 'https'),
frame:preprocess('{{lc:{{int:edit}}}}')
frame:preprocess('{{lc:{{int:edit}}}}')
Line 1,106: Line 1,155:
local compareLink
local compareLink
if compareUrl then
if compareUrl then
compareLink = makeUrlLink(
compareLink = p.makeUrlLink(
compareUrl,
compareUrl,
frame:preprocess('{{lc:{{int:diff}}}}')
frame:preprocess('{{lc:{{int:diff}}}}')
Line 1,112: Line 1,161:
end
end


sandboxLinks:node(makeToolbar(sandboxEditLink, compareLink))
sandboxLinks:node(p.makeToolbar(sandboxEditLink, compareLink))
elseif message('sandbox-subpage-show', nil, 'boolean') then
elseif p.message('sandbox-subpage-show', nil, 'boolean') then
sandboxLinks:wikitext(
sandboxLinks:wikitext(
i18n:msg('sandbox-link-display'),
i18n:msg('sandbox-link-display'),
Line 1,119: Line 1,168:
)
)


local sandboxCreateLink = makeUrlLink(
local sandboxCreateLink = p.makeUrlLink(
sandboxTitle:fullUrl({
sandboxTitle:fullUrl({
action  = 'edit',
action  = 'edit',
redlink = '1',
redlink = '1',
preload = subjectSpace == 828
preload = subjectSpace == 828
and message('module-sandbox-preload')
and p.message('module-sandbox-preload')
or  message('template-sandbox-preload'),
or  p.message('template-sandbox-preload'),
}, 'https'),
}, 'https'),
frame:preprocess('{{lc:{{int:create}}}}')
frame:preprocess('{{lc:{{int:create}}}}')
)
)
 
local mirrorPreload = p.message('mirror-link-preload')
local mirrorLink = makeUrlLink(
if templateTitle.namespace == 828 then
mirrorPreload = templateTitle.prefixedText
end
local mirrorLink = p.makeUrlLink(
sandboxTitle:fullUrl({
sandboxTitle:fullUrl({
action  = 'edit',
action  = 'edit',
redlink = '1',
redlink = '1',
preload = message('mirror-link-preload'),
preload = mirrorPreload,
summary = i18n:msg('mirror-edit-summary', makeWikilink(templatePage)),
summary = i18n:msg('mirror-edit-summary', p.makeWikilink(templatePage)),
}, 'https'),
}, 'https'),
i18n:msg('mirror-link-display')
i18n:msg('mirror-link-display')
)
)


sandboxLinks:node(makeToolbar(sandboxCreateLink, mirrorLink))
sandboxLinks:node(p.makeToolbar(sandboxCreateLink, mirrorLink))
else
else
sandboxLinks = nil
sandboxLinks = nil
end
end


if pageExists(testcasesTitle.prefixedText) then
if checkExists(testcasesTitle.prefixedText) then
testcasesLinks:wikitext(
testcasesLinks:wikitext(
makeUrlWikilink(testcasesTitle.prefixedText, i18n:msg('testcases-link-display')),
p.makeUrlWikilink(testcasesTitle.prefixedText, i18n:msg('testcases-link-display')),
' '
' '
)
)


local testcasesEditLink = makeUrlLink(
local testcasesEditLink = p.makeUrlLink(
testcasesTitle:fullUrl({ action = 'edit' }, 'https'),
testcasesTitle:fullUrl({ action = 'edit' }, 'https'),
frame:preprocess('{{lc:{{int:edit}}}}')
frame:preprocess('{{lc:{{int:edit}}}}')
)
)


testcasesLinks:node(makeToolbar(testcasesEditLink))
if env.subjectSpace == 828 then
local testcasesRunLink
local testcasesRunLinkDisplay = p.message('sandbox-notice-testcases-run-link-display')
local runTitle = testcasesTitle.talkPageTitle
if checkExists(runTitle.prefixedText) then
testcasesRunLink = p.makeUrlWikilink(runTitle.prefixedText, testcasesRunLinkDisplay)
else
testcasesRunLink = p.makeUrlLink(runTitle:fullUrl({
action = 'edit',
redlink = '1',
preload = p.message('module-testcases-run-preload'),
}), testcasesRunLinkDisplay)
end
testcasesLinks:node(p.makeToolbar(testcasesEditLink, testcasesRunLink))
else
testcasesLinks:node(p.makeToolbar(testcasesEditLink))
end
else
else
testcasesLinks:wikitext(
testcasesLinks:wikitext(
Line 1,163: Line 1,231:
)
)


local testcasesCreateLink = makeUrlLink(
local testcasesCreateLink = p.makeUrlLink(
testcasesTitle:fullUrl({
testcasesTitle:fullUrl({
action  = 'edit',
action  = 'edit',
redlink = '1',
redlink = '1',
preload = subjectSpace == 828
preload = subjectSpace == 828
and message('module-testcases-preload')
and p.message('module-testcases-preload')
or  message('template-testcases-preload'),
or  p.message('template-testcases-preload'),
}, 'https'),
}, 'https'),
frame:preprocess('{{lc:{{int:create}}}}')
frame:preprocess('{{lc:{{int:create}}}}')
)
)


testcasesLinks:node(makeToolbar(testcasesCreateLink))
testcasesLinks:node(p.makeToolbar(testcasesCreateLink))
end
end


Line 1,215: Line 1,283:
end
end


local docPathLink = makeUrlWikilink(
local docPathLink = p.makeUrlWikilink(
docTitle.prefixedText,
docTitle.prefixedText,
message('doc-link-display')
p.message('doc-link-display')
):gsub("%%", "%%%%")
):gsub("%%", "%%%%")


Line 1,245: Line 1,313:
end
end


local namespaceName = resolveNamespace(subjectSpace)
local namespaceName = p.resolveNamespace(subjectSpace)
assert(namespaceName ~= 'file')
assert(namespaceName ~= 'file')


return i18n:msg(
return i18n:msg(
resolveNamespace(subjectSpace) .. '-subpages-link',
p.resolveNamespace(subjectSpace) .. '-subpages-link',
'Special:PrefixIndex/' .. templateTitle.prefixedText .. '/'
'Special:PrefixIndex/' .. templateTitle.prefixedText .. '/'
)
)
Line 1,276: Line 1,344:
end
end
local ret
local ret
if pageExists(printTitle.prefixedText) then
if checkExists(printTitle.prefixedText) then
local printLink = makeWikilink(printTitle.prefixedText, message('print-link-display'))
local printLink = p.makeWikilink(printTitle.prefixedText, p.message('print-link-display'))
ret = message('print-blurb', {printLink})
ret = p.message('print-blurb', {printLink})
local displayPrintCategory = message('display-print-category', nil, 'boolean')
local displayPrintCategory = p.message('display-print-category', nil, 'boolean')
if displayPrintCategory then
if displayPrintCategory then
ret = ret .. makeCategoryLink(message('print-category'))
ret = ret .. p.makeCategoryLink(p.message('print-category'))
end
end
end
end
Line 1,324: Line 1,392:
local subpage = title.subpageText
local subpage = title.subpageText
local ret = ''
local ret = ''
if message('display-strange-usage-category', nil, 'boolean')
if p.message('display-strange-usage-category', nil, 'boolean')
and (
and (
subpage == message('doc-subpage')
subpage == p.message('doc-subpage')
or subjectSpace ~= 828 and subpage == message('testcases-subpage')
or subjectSpace ~= 828 and subpage == p.message('testcases-subpage')
)
)
then
then
ret = ret .. makeCategoryLink(message('strange-usage-category'))
ret = ret .. p.makeCategoryLink(p.message('strange-usage-category'))
end
end
local docStatus = env.docStatus
local docStatus = env.docStatus
if docStatus then
if docStatus then
ret = ret .. makeCategoryLink(message(docStatus .. '-category-' .. resolveNamespace(subjectSpace)))
ret = ret .. p.makeCategoryLink(p.message(docStatus .. '-category-' .. p.resolveNamespace(subjectSpace)))
end
if p.resolveNamespace(subjectSpace) ~= 'other' and subpage ~= p.message('sandbox-subpage') then
ret = ret .. p.makeCategoryLink(p.resolveNamespace(subjectSpace, true))
end
return ret
end
 
-- --------------------------------------------------------------------------
-- Type category
-- --------------------------------------------------------------------------
--[[
-- Add type if in arguments
-- @function p.addTypeCategory
-- @private
-- @param {table} args - a table of arguments passed by the user
-- @return {string}
--]]
function p.addTypeCategory(args)
if lib.isEmpty(args['type']) then return '' end
local templateTypes = lib.split(args['type'], ';')
local ret = ''
if templateTypes then
for _,Type in ipairs(templateTypes) do
ret = ret .. p.makeCategoryLink(Type .. ' Templates')
end
end
end
return ret
return ret
Line 1,340: Line 1,433:


return p
return p
-- </pre>

Revision as of 12:57, 17 December 2023

Documentation for this module may be created at Module:Documentation/doc

--------------------------------------------------------------------------------
-- Copied from Genshin Impact Wiki.
-- This module implements {{T|Documentation}}.
--
-- @module documentation
-- @alias p
-- @release stable
-- @require [[Global Lua Modules/Arguments|Dev:Arguments]]
-- @require [[Global Lua Modules/Config|Dev:Config]]
-- @require [[Global Lua Modules/I18n|Dev:I18n]]
-- @require [[Global Lua Modules/Loanguages|Dev:Languages]]
-- @require [[Global Lua Modules/Message box|Dev:Message box]]
-- @require [[Global Lua Modules/Yesno|Dev:Yesno]]
-- @require Module:Documentation/config
-- @require Module:Documentation/i18n
-- @require [[MediaWiki:Module:Documentation.css]]
-- @attribution [[wikipedia:Module:Documentation|Module:Documentation]] (Wikipedia)
-- @see [[wikipedia:Module:Documentation|Original module on Wikipedia]]
-- @see {{T|Documentation}}
--------------------------------------------------------------------------------

local libraryUtil = require('libraryUtil');
local checkType = libraryUtil.checkType;
local lib = require('Module:Feature')

-- Get required modules.
local getArgs = require('Module:Arguments').getArgs
local checkExists = require('Module:Exists').checkExists
local yesno = require('Module:Yesno')

-- Get the config table.
local cfg = require('Module:Config').loadConfig('Documentation')
local i18n = require('Module:I18n').loadMessages('Documentation')
local languages = require('Module:Languages')

local p = {}
p.i18n = i18n

-- Capitalizes first letter of strings
-- src: https://stackoverflow.com/questions/2421695/first-character-uppercase-lua
local function firstToUpper(str)
    return (str:gsub("^%l", string.upper))
end

-- --------------------------------------------------------------------------
-- Helper functions
--
-- These are defined as local functions, but are made available in the p
-- table for testing purposes.
-- --------------------------------------------------------------------------

--[[
-- Gets a message from the cfg table and formats it if appropriate.
-- The function raises an error if the value from the cfg table is not
-- of the type expectType. The default type for expectType is 'string'.
-- If the table valArray is present, strings such as $1, $2 etc. in the
-- message are substituted with values from the table keys [1], [2] etc.
-- For example, if the message "foo-message" had the value 'Foo $2 bar $1.',
-- p.message('foo-message', {'baz', 'qux'}) would return "Foo qux bar baz."
--
-- @function p.message
-- @private
-- @param {string} cfgKey
-- @param[opt] {table} valArray
-- @param[opt] {string} expectType
-- @return {string|number|boolean|table|nil}
--]]
function p.message(cfgKey, valArray, expectType)
	local msg = cfg:getValue(cfgKey)
	expectType = expectType or 'string'
	if type(msg) ~= expectType then
		error('message: type error in message cfg.' .. cfgKey .. ' (' .. expectType .. ' expected, got ' .. type(msg) .. ')', 2)
	end
	if not valArray then
		return msg
	end

	function getMessageVal(match)
		match = tonumber(match)
		return valArray[match] or error('message: no value found for key $' .. match .. ' in message cfg.' .. cfgKey, 4)
	end

	local ret = mw.ustring.gsub(msg, '$([1-9][0-9]*)', getMessageVal)
	return ret
end

function p.makeWikilink(page, display)
	if display then
		return mw.ustring.format('[[%s|%s]]', page, display)
	else
		return mw.ustring.format('[[%s]]', page)
	end
end

function p.makeUrlWikilink(page, display)
	-- This code prevents redlinks, but doesn't work for categories, and those
	-- are always blue-linked by the CategoryBlueLinks extension anyway.
	return mw.ustring.format('[%s %s]', tostring(mw.uri.fullUrl(page)), display or page)
end


function p.makeCategoryLink(cat, sort)
	local catns = mw.site.namespaces[14].name
	return p.makeWikilink(catns .. ':' .. cat, sort)
end

function p.makeUrlLink(url, display)
	return mw.ustring.format('[%s %s]', url, display)
end

function p.makeToolbar(...)
	local lim = select('#', ...)
	if lim < 1 then
		return nil
	end

	local frame = mw.getCurrentFrame()
	local ret = mw.html.create('small'):wikitext('(')

	local isFirst = true
	for i = 1, lim do
		local v = select(i, ...)
		if v then
			if isFirst then
				isFirst = false
			else
				ret:wikitext(' ', frame:preprocess('{{int:pipe-separator}}'), '&nbsp;')
			end
			ret:wikitext(v)
		end
	end

	return not isFirst and ret:wikitext(')'):done() or nil
end

--[[
-- @function p.resolveNamespace
-- @private
-- @param {int} subjectSpace
-- @return {string}
--]]
function p.resolveNamespace(subjectSpace, upperPlural)
	if subjectSpace == 10 then -- Template namespace
		if upperPlural then	return 'Templates'
		else return 'template' end
	elseif subjectSpace == 828 then -- Module namespace
		if upperPlural then return 'Modules'
		else return 'module' end
	elseif subjectSpace == 6 then -- File namespace
		if upperPlural then return 'Files'
		else return 'file' end
	end
	return 'other'
end

-- --------------------------------------------------------------------------
-- Argument processing
-- --------------------------------------------------------------------------

function p.makeInvokeFunc(funcName)
	return function (frame)
		local args = getArgs(frame, {
			valueFunc = function (key, value)
				if type(value) == 'string' then
					value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
					if key == 'heading' or value ~= '' then
						return value
					else
						return nil
					end
				else
					return value
				end
			end
		})
		return tostring(p[funcName](args))
	end
end

-- --------------------------------------------------------------------------
-- Main function
-- --------------------------------------------------------------------------

--[[
-- This function defines logic flow for the module.
--
-- ; Messages:
-- : 'main-div-id' --> 'template-documentation'
-- : 'main-div-classes' --> 'template-documentation iezoomfix'
--
-- @function p.main
-- @param {table|Frame} args - table of arguments passed by the user
-- @return {string}
--]]
p.main = p.makeInvokeFunc('_main')

function p._main(args)
	local env = p.getEnvironment(args)
	if env.title == env.testcasesTitle then
		if env.title.namespace == 828 then
			-- we are in module namespace, the testcase title is equal, so expand the testcase header
			local root = mw.html.create()
			root:wikitext(mw.getCurrentFrame():expandTemplate{title = 'Documentation/Testcase Header'})
			return root
		end
	else
		local root = mw.html.create()
		root
			:wikitext(p.protectionTemplate(env))
			:wikitext(p.sandboxNotice(args, env))
			:tag('div')
				:attr('id', p.message('main-div-id'))
				:addClass(p.message('main-div-classes'))
				:newline()
				:node(p._startBox(args, env))
				:node(p._navigation(args, env))
				:node(p._content(args, env))
				:node(p._endBox(args, env))
			:done()
			:wikitext(p.addTrackingCategories(env))
			:wikitext(p.addTypeCategory(args))
		return root
	end
end

-- --------------------------------------------------------------------------
-- Environment settings
-- --------------------------------------------------------------------------

--- A table with information about the environment, including title objects and
-- other namespace- or path-related data.
-- @type Environment
--
-- @note All table lookups are passed through pcall so that errors are caught.
-- If an error occurs, the value returned will be nil.
--
-- ; Title objects include:
--- @property {Title|nil} Environment.title - the page we are making documentation for (usually the current title)
--- @property {Title|nil} Environment.templateTitle - the template (or module, file, etc.)
--- @property {Title|nil} Environment.docTitle - the /doc subpage.
--- @property {Title|nil} Environment.sandboxTitle - the /sandbox subpage.
--- @property {Title|nil} Environment.testcasesTitle - the /testcases subpage.
--- @property {Title|nil} Environment.printTitle - the print version of the template, located at the /Print subpage.
--
-- ; Data includes:
--- @property {Title.protectionLevels|nil} Environment.protectionLevels - the protection levels table of the title object.
--- @property {int|nil} Environment.subjectSpace - the number of the title's subject namespace.
--- @property {int|nil} Environment.docSpace - the number of the namespace the title puts its documentation in.
--- @property {string|nil} Environment.docpageBase - the text of the base page of the /doc, /sandbox and /testcases pages, with namespace.
--- @property {string|nil} Environment.compareUrl - URL of the Special:ComparePages page comparing the sandbox with the template.

--[[
-- Returns a table with information about the environment, including title objects and other namespace- or
-- path-related data.
--
-- @function p.getEnvironment
-- @private
-- @param {table} args - table of arguments passed by the user
-- @return {Environment}
--]]
function p.getEnvironment(args)
	local env, envFuncs = {}, {}

	-- Set up the metatable. If triggered we call the corresponding function in the envFuncs table. The value
	-- returned by that function is memoized in the env table so that we don't call any of the functions
	-- more than once. (Nils won't be memoized.)
	setmetatable(env, {
		__index = function (t, key)
			local envFunc = envFuncs[key]
			if envFunc then
				local success, val = pcall(envFunc)
				if success then
					env[key] = val -- Memoise the value.
					return val
				end
			end
			return nil
		end
	})

	-- The title object for the current page, or a test page passed with args.page.
	function envFuncs.title()
		local title
		local titleArg = args.page
		if titleArg then
			title = mw.title.new(titleArg)
		else
			title = mw.title.getCurrentTitle()
		end
		return title
	end

	-- The template (or module, etc.) title object.
	-- ; Messages:
	-- : 'sandbox-subpage' --> 'sandbox'
	-- : 'testcases-subpage' --> 'testcases'
	function envFuncs.templateTitle()
		local subjectSpace = env.subjectSpace
		local title = env.title
		local subpage = title.subpageText
		if subpage == p.message('sandbox-subpage') or subpage == p.message('testcases-subpage') or subpage == p.message('doc-subpage') then
			return mw.title.makeTitle(subjectSpace, title.baseText)
		else
			return mw.title.makeTitle(subjectSpace, title.text)
		end
	end

	-- Title object of the /doc subpage.
	-- ; Messages:
	-- : 'doc-subpage' --> 'doc'
	function envFuncs.docTitle()
		local docname = args[1] -- User-specified doc page.
		local docpage
		if docname then
			docpage = docname
		else
			docpage = env.docpageBase .. '/' .. p.message('doc-subpage')
		end
		return mw.title.new(docpage)
	end

	function envFuncs.subpages()
		local docTitle = env.docTitle
		return languages.subpages(docTitle.text, docTitle.nsText)
	end

	function envFuncs.hasSubpages()
		local subpages = env.subpages
		return #subpages > 1 or subpages[1] ~= ''
	end

	function envFuncs.docTitleCreate()
		local hasSubpages = env.hasSubpages
		local docTitle = env.docTitle
		return docTitle
	end

	function envFuncs.docTitleCurrentLang()
		local currentLang = i18n:getLang()
		if currentLang == 'en' or yesno(args.ignoreCurrentLang) then
			return env.docTitleCreate
		else
			local docTitle = env.docTitle
			return mw.title.new(docTitle.text .. '/' .. currentLang, docTitle.nsText)
		end
	end

	-- Title object for the /sandbox subpage.
	-- ; Messages:
	-- : 'sandbox-subpage' --> 'sandbox'
	function envFuncs.sandboxTitle()
	    -- return nil
		return mw.title.new(env.docpageBase .. '/' .. p.message('sandbox-subpage'))
	end

	-- Title object for the /testcases subpage.
	-- ; Messages:
	-- : 'testcases-subpage' --> 'testcases'
	function envFuncs.testcasesTitle()
	    -- return nil
		return mw.title.new(env.docpageBase .. '/' .. p.message('testcases-subpage'))
	end

	-- Title object for the /Print subpage.
	-- ; Messages:
	-- : 'print-subpage' --> 'Print'
	function envFuncs.printTitle()
		if p.message('print-show') then
			return env.templateTitle:subPageTitle(p.message('print-subpage'))
		end
	end

	-- The protection levels table of the title object.
	function envFuncs.protectionLevels()
		return env.title.protectionLevels
	end

	-- The subject namespace number.
	function envFuncs.subjectSpace()
		return mw.site.namespaces[env.title.namespace].subject.id
	end

	-- The documentation namespace number. For most namespaces this is the same as the
	-- subject namespace. However, pages in the Article, File, MediaWiki or Category
	-- namespaces must have their /doc, /sandbox and /testcases pages in talk space.
	function envFuncs.docSpace()
		local subjectSpace = env.subjectSpace
		if subjectSpace == 0 or subjectSpace == 6 or subjectSpace == 8 or subjectSpace == 14 then
			return subjectSpace + 1
		else
			return subjectSpace
		end
	end

	-- The base page of the /doc, /sandbox, and /testcases subpages.
	-- For some namespaces this is the talk page, rather than the template page.
	function envFuncs.docpageBase()
		local templateTitle = env.templateTitle
		local docSpace = env.docSpace
		local docSpaceText = mw.site.namespaces[docSpace].name
		-- Assemble the link. docSpace is never the main namespace, so we can hardcode the colon.
		return docSpaceText .. ':' .. templateTitle.text
	end

	-- Diff link between the sandbox and the main template using [[Special:ComparePages]].
	function envFuncs.compareUrl()
		local templateTitle = env.templateTitle
		local sandboxTitle = env.sandboxTitle
		if checkExists(templateTitle.prefixedText) and checkExists(sandboxTitle.prefixedText) then
			local compareUrl = mw.uri.fullUrl(
				'Special:ComparePages',
				{page1 = templateTitle.prefixedText, page2 = sandboxTitle.prefixedText}
			)
			return tostring(compareUrl)
		else
			return nil
		end
	end

	function envFuncs.docStatus()
		local docTitle = env.docTitle
		if not args.content then
			if not checkExists(docTitle.prefixedText) and not env.hasSubpages then
				return 'nodoc'
			elseif not checkExists(docTitle.prefixedText) and env.hasSubpages then
				return 'baddoc'
			end
		end
	end

	function envFuncs.docIcon()
		local docStatus = env.docStatus
		if not docStatus then
			return p.message('documentation-icon')
		end
		return p.message('documentation-icon-' .. docStatus)
	end
		function envFuncs.title()
		-- The title object for the current page, or a test page passed with args.page.
		local title
		local titleArg = args.page
		if titleArg then
			title = mw.title.new(titleArg)
		else
			title = mw.title.getCurrentTitle()
		end
		return title
	end
	return env
end

-- --------------------------------------------------------------------------
-- Auxiliary templates
-- --------------------------------------------------------------------------

---
-- Generates a sandbox notice for display above sandbox pages.
--
-- ; Messages:
-- : 'sandbox-notice-image' --> '[[Image:Sandbox.svg|50px|alt=|link=]]'
-- : 'sandbox-notice-blurb' --> 'This is the $1 for $2.'
-- : 'sandbox-notice-diff-blurb' --> 'This is the $1 for $2 ($3).'
-- : 'sandbox-notice-pagetype-template' --> '[[Wikipedia:Template test cases|template sandbox]] page'
-- : 'sandbox-notice-pagetype-module' --> '[[Wikipedia:Template test cases|module sandbox]] page'
-- : 'sandbox-notice-pagetype-other' --> 'sandbox page'
-- : 'sandbox-notice-compare-link-display' --> 'diff'
-- : 'sandbox-notice-testcases-blurb' --> 'See also the companion subpage for $1.'
-- : 'sandbox-notice-testcases-link-display' --> 'test cases'
-- : 'sandbox-category' --> 'Template sandboxes'
--
-- @function p.sandboxNotice
-- @private
-- @param {table} args - a table of arguments passed by the user
-- @param {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string|nil}
function p.sandboxNotice(args, env)
	if not p.message('sandbox-notice-show', nil, 'boolean') then
		return nil
	end

	local title = env.title
	local sandboxTitle = env.sandboxTitle
	local templateTitle = env.templateTitle
	local subjectSpace = env.subjectSpace
	if not (subjectSpace and title and sandboxTitle and templateTitle and mw.title.equals(title, sandboxTitle)) then
		return nil
	end
	-- Build the table of arguments to pass to {{T|ombox}}. We need just two fields, "image" and "text".
	local omargs = {}
	omargs.image = p.message('sandbox-notice-image')
	-- Get the text. We start with the opening blurb, which is something like
	-- "This is the template sandbox for [[Template:Foo]] (diff)."
	local header = ''
	local text = ''
	local frame = mw.getCurrentFrame()
	local isPreviewing = frame:preprocess('{{REVISIONID}}') == '' -- True if the page is being previewed.
	local pagetype
	if subjectSpace == 10 then
		pagetype = p.message('sandbox-notice-pagetype-template')
	elseif subjectSpace == 828 then
		pagetype = p.message('sandbox-notice-pagetype-module')
	else
		pagetype = p.message('sandbox-notice-pagetype-other')
	end
	local templateLink = p.makeUrlWikilink(templateTitle.prefixedText)
	local compareUrl = env.compareUrl
	if isPreviewing or not compareUrl then
		header = header .. p.message('sandbox-notice-blurb', {pagetype, templateLink})
	else
		local compareDisplay = p.message('sandbox-notice-compare-link-display')
		local compareLink = p.makeUrlLink(compareUrl, compareDisplay)
		header = header .. p.message('sandbox-notice-diff-blurb', {pagetype, templateLink, compareLink})
	end
	-- Get the test cases page blurb if the page exists. This is something like
	-- "See also the companion subpage for [[Template:Foo/testcases|test cases]]."
	local testcasesTitle = env.testcasesTitle
	if testcasesTitle and checkExists(testcasesTitle.prefixedText) then
		if testcasesTitle.namespace == mw.site.namespaces.Module.id then
			local testcasesLinkDisplay = p.message('sandbox-notice-testcases-link-display')
			local testcasesRunLinkDisplay = p.message('sandbox-notice-testcases-run-link-display')
			local testcasesLink = p.makeUrlWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
			local runTitle = testcasesTitle.talkPageTitle
			local testcasesRunLink
			if checkExists(runTitle.prefixedText) then
				testcasesRunLink = p.makeUrlWikilink(runTitle.prefixedText, testcasesRunLinkDisplay)
			else
				testcasesRunLink = p.makeUrlLink(runTitle:fullUrl({
					action = 'edit',
					redlink = '1',
					preload = p.message('module-testcases-run-preload'),
				}), testcasesRunLinkDisplay)
			end
			local testcasesRunLink = p.makeUrlWikilink(testcasesTitle.talkPageTitle.prefixedText, testcasesRunLinkDisplay)
			text = text .. p.message('sandbox-notice-testcases-run-blurb', {testcasesLink, testcasesRunLink})
		else
			local testcasesLinkDisplay = p.message('sandbox-notice-testcases-link-display')
			local testcasesLink = p.makeUrlWikilink(testcasesTitle.prefixedText, testcasesLinkDisplay)
			text = text .. p.message('sandbox-notice-testcases-blurb', {testcasesLink})
		end
	end
	-- Add the sandbox to the sandbox category.
	if title.namespace == 828 then
		text = text .. p.makeCategoryLink(p.message('sandbox-module-category'))
	else
		text = text .. p.makeCategoryLink(p.message('sandbox-category'))
	end
	omargs.header = header
	omargs.text = text
	omargs.imagewidth = '50px'
	omargs.class = 'sandbox'
	local ret = '<div style="clear: both;"></div>'
	ret = ret .. frame:expandTemplate{ title = 'Mbox', args = omargs}
	return ret
end

--[[
-- Generates the padlock icon in the top right.
--
-- ; Messages:
-- : 'protection-template' --> 'pp-template'
-- : 'protection-template-args' --> {docusage = 'yes'}
--
-- @function p.protectionTemplate
-- @private
-- @param {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string|nil}
--]]
function p.protectionTemplate(env)
	-- This depends on [[Module:Protection banner]]
	do return end

	local protectionLevels, mProtectionBanner
	local title = env.title
	if title.namespace ~= 10 and title.namespace ~= 828 then
		-- Don't display the protection template if we are not in the template or module namespaces.
		return nil
	end
	protectionLevels = env.protectionLevels
	if not protectionLevels then
		return nil
	end
	local editProt = protectionLevels.edit and protectionLevels.edit[1]
	local moveProt = protectionLevels.move and protectionLevels.move[1]
	if editProt then
		-- The page is edit-protected.
		mProtectionBanner = require('Dev:Protection banner')
		local reason = p.message('protection-reason-edit')
		return mProtectionBanner._main{reason, small = true}
	elseif moveProt and moveProt ~= 'autoconfirmed' then
		-- The page is move-protected but not edit-protected. Exclude move
		-- protection with the level "autoconfirmed", as this is equivalent to
		-- no move protection at all.
		mProtectionBanner = require('Dev:Protection banner')
		return mProtectionBanner._main{action = 'move', small = true}
	else
		return nil
	end
end

-- --------------------------------------------------------------------------
-- Start box
-- --------------------------------------------------------------------------

--[[
-- This function generates the start box.
--
-- The actual work is done by p.makeStartBoxLinksData and p.renderStartBoxLinks which make
-- the [view] [edit] [history] [purge] links, and by p.makeStartBoxData and p.renderStartBox
-- which generate the box HTML.
--
-- @function p.startBox
-- @param {table|Frame} args - a table of arguments passed by the user
-- @param[opt] {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string|nil}
--]]
p.startBox = p.makeInvokeFunc('_startBox')

function p._startBox(args, env)
	env = env or p.getEnvironment(args)
	local links
	local content = args.content
	if not content then
		-- No need to include the links if the documentation is on the template page itself.
		local linksData = p.makeStartBoxLinksData(args, env)
		if linksData then
			links = p.renderStartBoxLinks(linksData)
		end
	end
	-- Generate the start box html.
	local data = p.makeStartBoxData(args, env, links)
	if data then
		return p.renderStartBox(data)
	else
		-- User specified no heading.
		return nil
	end
end

--[[
-- Does initial processing of data to make the [view] [edit] [history] [purge] links.
--
-- ; Messages:
-- : 'file-docpage-preload' --> 'Template:Documentation/preload-filespace'
-- : 'module-preload' --> 'Template:Documentation/preload-module-doc'
-- : 'docpage-preload' --> 'Template:Documentation/preload'
--
-- @function p.makeStartBoxLinksData
-- @private
-- @param {table} args - a table of arguments passed by the user
-- @param {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {table|nil}
--]]
function p.makeStartBoxLinksData(args, env)
	local subjectSpace = env.subjectSpace
	local title = env.title
	local docTitleCurrentLang = env.docTitleCurrentLang
	if not title or not docTitleCurrentLang then
		return nil
	end

	local frame = mw.getCurrentFrame()
	local data = {}
	data.title = title
	data.docTitle = docTitleCurrentLang
	-- View, display, edit, and purge links if /doc exists.
	data.viewLinkDisplay = frame:preprocess('{{lc:{{int:view}}}}')
	data.editLinkDisplay = frame:preprocess('{{lc:{{int:edit}}}}')
	data.historyLinkDisplay = frame:preprocess('{{lc:{{int:history_short}}}}')
	data.purgeLinkDisplay = frame:preprocess('{{lc:{{int:page-header-action-button-purge}}}}')
	-- Create link if /doc doesn't exist.
	local preload = args.preload
	if not preload then
		if subjectSpace == 6 then -- File namespace
			preload = p.message('file-docpage-preload')
		elseif subjectSpace == 828 then -- Module namespace
			preload = p.message('module-preload')
		else
			preload = p.message('docpage-preload')
		end
	end
	data.preload = preload
	data.createLinkDisplay = frame:preprocess('{{lc:{{int:create}}}}')
	return data
end

--[[
-- Generates the [view][edit][history][purge] or [create] links from the data table.
--
-- @function p.renderStartBoxLinks
-- @private
-- @param {table} data - a table of data generated by p.makeStartBoxLinksData
-- @return {string}
--]]
function p.renderStartBoxLinks(data)

	function escapeBrackets(s)
		-- Escapes square brackets with HTML entities.
		s = s:gsub('%[', '&#91;') -- Replace square brackets with HTML entities.
		s = s:gsub('%]', '&#93;')
		return s
	end

	local ret
	local docTitle = data.docTitle
	local title = data.title
	if checkExists(docTitle.prefixedText) then
		local viewLink = p.makeWikilink(docTitle.prefixedText, data.viewLinkDisplay)
		local editLink = p.makeUrlLink(docTitle:fullUrl({ action = 'edit' }, 'https'), data.editLinkDisplay)
		local historyLink = p.makeUrlLink(docTitle:fullUrl({ action = 'history' }, 'https'), data.historyLinkDisplay)
		local purgeLink = p.makeUrlLink(title:fullUrl({ action = 'purge' }, 'https'), data.purgeLinkDisplay)
		ret = '[%s] [%s] [%s] [%s]'
		ret = escapeBrackets(ret)
		ret = mw.ustring.format(ret, viewLink, editLink, historyLink, purgeLink)
	else
		local createLink = p.makeUrlLink(docTitle:fullUrl({
			action = 'edit',
			redlink = '1',
			preload = data.preload,
		}, 'https'), data.createLinkDisplay)
		local purgeLink = p.makeUrlLink(title:fullUrl({ action = 'purge' }, 'https'), data.purgeLinkDisplay)
		ret = '[%s] [%s]'
		ret = escapeBrackets(ret)
		ret = mw.ustring.format(ret, createLink, purgeLink)
	end
	return ret
end

---
-- Does initial processing of data to pass to the start-box render function, p.renderStartBox.
--
-- ; Messages:
-- : 'documentation-icon-wikitext' --> '[[File:$1|50px|link=|alt=Documentation icon]]'
-- : 'documentation-icon' --> 'Template-info.svg'
-- : 'documentation-icon-nodoc' --> 'Template-noinfo.svg'
-- : 'documentation-icon-baddoc' --> 'Template-badinfo.svg'
-- : 'start-box-linkclasses' --> 'mw-editsection-like plainlinks'
-- : 'start-box-link-id' --> 'doc_editlinks'
--
-- @function p.makeStartBoxData
-- @private
-- @param {table} args - a table of arguments passed by the user
-- @param {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @param {string|nil} links - a string containing the [view][edit][history][purge] links - could be nil if there's an error.
-- @return {table}
function p.makeStartBoxData(args, env, links)
	local subjectSpace = env.subjectSpace
	if not subjectSpace then
		-- Default to an "other namespaces" namespace, so that we get at least some output
		-- if an error occurs.
		subjectSpace = 2
	end
	local data = {}

	-- Heading
	local heading = args.heading -- Blank values are not removed.
	if heading == '' then
		-- Don't display the start box if the heading arg is defined but blank.
		return nil
	end

	if heading then
		data.heading = heading
	elseif subjectSpace == 10 then -- Template namespace
		data.heading = p.message('documentation-icon-wikitext', {env.docIcon}) .. ' ' .. i18n:msg('documentation-heading')
		data.subHeading = i18n:msg('documentation-visibility')
	elseif subjectSpace == 828 then -- Module namespace
		data.heading = p.message('documentation-icon-wikitext', {env.docIcon}) .. ' ' .. i18n:msg('module-namespace-heading')
	elseif subjectSpace == 6 then -- File namespace
		data.heading = i18n:msg('file-namespace-heading')
	else
		data.heading = i18n:msg('other-namespaces-heading')
	end

	-- Heading CSS
	local headingStyle = args['heading-style']
	if headingStyle then
		data.headingStyleText = headingStyle
	elseif subjectSpace == 10 then
		-- We are in the template or template talk namespaces.
		data.headingFontWeight = 'bold'
		data.headingFontSize = '100%'
	else
		data.headingFontSize = '100%'
	end

	-- Data for the [view][edit][history][purge] or [create] links.
	if links then
		data.linksClass = p.message('start-box-linkclasses')
		data.linksId = p.message('start-box-link-id')
		data.links = links
	end

	return data
end

--[[
-- Renders the start box html.
--
-- ; Messages
-- : 'start-box-div-classes' --> 'template-documentation-header'
--
-- @function p.renderStartBox
-- @private
-- @param {table} data - a table of data generated by p.makeStartBoxData.
-- @return {string}
--]]
function p.renderStartBox(data)
	local sbox = mw.html.create('div')
	sbox
		:addClass(p.message('start-box-div-classes'))
		:newline()
		:tag('span')
			:cssText(data.headingStyleText)
			:css('font-weight', data.headingFontWeight)
			:css('font-size', data.headingFontSize)
			:wikitext(data.heading)
	local links = data.links
	if links then
		sbox
			:tag('div')
			:css('font-size', data.headingFontSize)
			:css('float', 'right')
				:tag('span')
				:addClass(data.linksClass)
				:attr('id', data.linksId)
				:wikitext(links)
	end
	local subHeading = data.subHeading
	if subHeading then
		sbox
			:tag('br')
			:css('clear', 'right')
		sbox
			:tag('i')
			:wikitext(subHeading)
	end
	return sbox
end

p.navigation = p.makeInvokeFunc('_navigation')

function p._navigation(args, env)
	-- Get environment data.
	env = env or p.getEnvironment(args)
	local frame = mw.getCurrentFrame()
	local nav = mw.html.create('table')
	nav
		:addClass('doctable')
		:css('width', '100%')
	local navhead = mw.html.create('tr')
		:tag('th')
		:addClass('doc-header')
	local colspan = 0
	if args.navHeader ~= nil then
		navhead:wikitext(args.navHeader):done()
	end
	local lnkdta = mw.html.create('tr'):addClass('links')
	local tags = {}
	tags[1] = lnkdta:tag('td')
	tags[1]:wikitext('[[' .. env.templateTitle.prefixedText .. '|Main]]')
	colspan = colspan + 1
	if checkExists(env.docTitle.prefixedText) then
		tags[2] = lnkdta:tag('td')
		tags[2]:wikitext('[[' .. env.docTitle.prefixedText .. '|' .. firstToUpper(p.message('doc-link-display')) .. ']]')
		colspan = colspan + 1
	end
	if checkExists(env.sandboxTitle.prefixedText) then
		tags[3] = lnkdta:tag('td')
		tags[3]:wikitext('[[' .. env.sandboxTitle.prefixedText .. '|' .. firstToUpper(i18n:msg('sandbox-link-display')) .. ']]')
		colspan = colspan + 1
	end
	if checkExists(env.testcasesTitle.prefixedText) then
		tags[4] = lnkdta:tag('td')
		tags[4]:wikitext('[[' .. env.testcasesTitle.prefixedText ..  '|' .. firstToUpper(i18n:msg('testcases-link-display')) .. ']]')
		colspan = colspan + 1
	end
	tags[5] = lnkdta:tag('td')
	tags[5]:wikitext('[' .. tostring(mw.uri.fullUrl('Special:WhatLinksHere', 'hidelinks=1&hideredirs=1&target=' .. env.templateTitle.prefixedText)) .. ' Usage]')
	colspan = colspan + 1
	for k,v in pairs(tags) do
		v:css('width', (100 / colspan) .. '%')
	end
	navhead:attr('colspan', colspan)
	if args.navHeader ~= nil then
		nav:node(navhead)
	end
	nav:node(lnkdta)
	nav:allDone()
	return nav
end

-- --------------------------------------------------------------------------
-- Documentation content
-- --------------------------------------------------------------------------

--[[
-- Displays the documentation contents
--
-- ; Messages
-- : 'content-box-div-classes' --> 'template-documentation-content'
--
-- @function p.content
-- @param {table|Frame} args - a table of arguments passed by the user
-- @param[opt] {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string}
--]]
p.content = p.makeInvokeFunc('_content')

function p._content(args, env)
	env = env or p.getEnvironment(args)
	local docTitle = env.docTitle
	local content = args.content
	local root = mw.html.create()
	local frame = mw.getCurrentFrame()
	if not content and docTitle then
		local subjectSpace = env.subjectSpace
		local preload = args.preload
		if not preload then
			if subjectSpace == 6 then -- File namespace
				preload = p.message('file-docpage-preload')
			elseif subjectSpace == 828 then -- Module namespace
				preload = p.message('module-preload')
			else
				preload = p.message('docpage-preload')
			end
		end

		local hasSubpages = env.hasSubpages
		local docTitleCreate = env.docTitleCreate

		local docMissing = i18n:msg(
			p.resolveNamespace(subjectSpace) .. '-documentation-missing',
			docTitleCreate:fullUrl({
				action = 'edit',
				redlink = '1',
				preload = mw.uri.encode(preload, 'WIKI'),
			}, 'https'):gsub('%%', '%%%%')
		)

		if not checkExists(docTitle.prefixedText) and not hasSubpages then
			content = docMissing
		else
			if yesno(args.i18n, false) or hasSubpages then
				local currentLang = i18n:getLang()
				root:node(languages.langs{
					'en',
					currentLang,
					format = 'list',
					page = docTitle.prefixedText,
					class = p.message('languages-list-div-classes'),
					select = currentLang,
					editintro = 'Template:Documentation/editintro',
				})
			end
			if not pcall(function()
				content = args._content or frame:expandTemplate{title = docTitle.prefixedText}
			end) then
				content = '[[' .. docTitle.prefixedText .. ']]'
			end
		end
	end
	local cbox = root:tag('div')
	cbox
		:addClass(p.message('content-box-div-classes'))
		:css("display", "flow-root")
		-- The line breaks are necessary so that "=== Headings ===" at the start
		-- and end of docs are interpreted correctly.
		:wikitext(mw.getCurrentFrame():expandTemplate{title = p.message('template-tocright')})
		:newline()
		:wikitext(content or '')
		:newline()
	return root
end

--[[
-- Gets the content title
--
-- @function p.contentTitle
-- @param {table|Frame} args - a table of arguments passed by the user
-- @param[opt] {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string}
--]]
p.contentTitle = p.makeInvokeFunc('_contentTitle')

function p._contentTitle(args, env)
	env = env or p.getEnvironment(args)
	local docTitle = env.docTitle
	if not args.content and docTitle and checkExists(docTitle.prefixedText) then
		return docTitle.prefixedText
	else
		return ''
	end
end

-- --------------------------------------------------------------------------
-- End box
-- --------------------------------------------------------------------------

---
-- This function generates the end box (also known as the link box).
--
-- The HTML is generated by the {{T|fmbox}} template, courtesy of [[Module:Message box]].
--
-- ; Messages:
-- : 'fmbox-id' --> 'documentation-meta-data'
-- : 'fmbox-style' --> 'background-color: #ecfcf4'
-- : 'fmbox-textstyle' --> 'font-style: italic'
--
-- @function p.endBox
-- @param {table|Frame} args - a table of arguments passed by the user
-- @param[opt] {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string}
p.endBox = p.makeInvokeFunc('_endBox')

function p._endBox(args, env)
	-- Get environment data.
	env = env or p.getEnvironment(args)

	local subjectSpace = env.subjectSpace
	local docTitle = env.docTitleCurrentLang

	local root = mw.html.create('div')
		:attr('id', p.message('end-box-div-id'))
		:addClass(p.message('end-box-div-classes'))
		:css('clear', 'both')

	local hasDocPage   = subjectSpace and docTitle and checkExists(docTitle.prefixedText)
	local linkBoxSpace = subjectSpace == 2 or subjectSpace == 10 or subjectSpace == 828

	-- Check whether we should output the end box at all. Add the end
	-- box by default if the documentation exists or if we are in the
	-- user, module or template namespaces.
	local linkBox = args['link box']
	local expandedLinkBox = yesno(linkBox, true)

	if (
		not hasDocPage and not linkBoxSpace
		and not (linkBox and yesno(linkBox) == nil)
	) then
		return root
			:attr('id', nil)
			:attr('class', nil)
		:allDone()
	end

	local customLanguagesNotice = i18n:msg('custom-languages-notice', docTitle.prefixedText,
		docTitle:fullUrl({ action = 'edit' }, 'https'):gsub("%%", "%%%%"), 'bottom')

	if expandedLinkBox == false then
		return root:wikitext(customLanguagesNotice):allDone()
	end

	if linkBox and yesno(linkBox) == nil then
		root:wikitext(linkBox)
	else
		local isFirstLine = true
		if (not args.content) and hasDocPage then
			isFirstLine = false
			root:wikitext(customLanguagesNotice)
		end

		if linkBoxSpace then
			-- We are in the user, template or module namespaces.
			-- Add sandbox and testcases links:
			-- "Editors can experiment in this template's sandbox (edit | history / create | mirror) and testcases (edit/create) pages."
			local experimentBlurb = p.makeExperimentBlurb(args, env)
			if experimentBlurb then
				if isFirstLine then
					isFirstLine = false
				else
					root:tag('br')
				end
				root:node(experimentBlurb)
			end

			-- "Please add categories to the /doc subpage."
			-- Don't show this message with inline docs or with an explicitly specified doc page,
			-- as then it is unclear where to add the categories.
			local categoriesBlurb
			if not args.content and not args[1] then
				categoriesBlurb = p.makeCategoriesBlurb(args, env)
			end

			-- "Subpages of this template"
			local subpagesBlurb = p.makeSubpagesBlurb(args, env)

			if categoriesBlurb or subpagesBlurb then
				if isFirstLine then
					isFirstLine = false
				else
					root:tag('br'):done()
				end

				root:node(categoriesBlurb)

				if categoriesBlurb and subpagesBlurb then
					root:wikitext(' ');
				end

				root:node(subpagesBlurb)
			end

			-- TODO: consider adding the print blurb
		end
	end

	return root:allDone()
end

--[[
-- Renders the text "Editors can experiment in this template's sandbox (edit | diff) and testcases (edit) pages."
--
-- ; Messages:
-- : 'module-sandbox-preload' --> 'Template:Documentation/preload-module-sandbox'
-- : 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
-- : 'mirror-link-preload' --> 'Template:Documentation/mirror'
-- : 'template-sandbox-preload' --> 'Template:Documentation/preload-sandbox'
-- : 'module-testcases-preload' --> 'Template:Documentation/preload-module-testcases'
-- : 'template-testcases-preload' --> 'Template:Documentation/preload-testcases'
--
-- @function p.makeExperimentBlurb
-- @private
-- @param {table} args - a table of arguments passed by the user
-- @param {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string|nil}
--]]
function p.makeExperimentBlurb(args, env)
	local subjectSpace = env.subjectSpace
	local templateTitle = env.templateTitle
	local sandboxTitle = env.sandboxTitle
	local testcasesTitle = env.testcasesTitle

	if not subjectSpace or not templateTitle or not sandboxTitle or not testcasesTitle then
		return nil
	end

	local templatePage = templateTitle.prefixedText

	-- Make links.
	local frame = mw.getCurrentFrame()
	local sandboxLinks = mw.html.create()
	local testcasesLinks = mw.html.create()

	if checkExists(sandboxTitle.prefixedText) then
		sandboxLinks:wikitext(
			p.makeUrlWikilink(sandboxTitle.prefixedText, i18n:msg('sandbox-link-display')),
			' '
		)

		local sandboxEditLink = p.makeUrlLink(
			sandboxTitle:fullUrl({ action = 'edit' }, 'https'),
			frame:preprocess('{{lc:{{int:edit}}}}')
		)
		local compareUrl = env.compareUrl
		local compareLink
		if compareUrl then
			compareLink = p.makeUrlLink(
				compareUrl,
				frame:preprocess('{{lc:{{int:diff}}}}')
			)
		end

		sandboxLinks:node(p.makeToolbar(sandboxEditLink, compareLink))
	elseif p.message('sandbox-subpage-show', nil, 'boolean') then
		sandboxLinks:wikitext(
			i18n:msg('sandbox-link-display'),
			' '
		)

		local sandboxCreateLink = p.makeUrlLink(
			sandboxTitle:fullUrl({
				action  = 'edit',
				redlink = '1',
				preload = subjectSpace == 828
					and p.message('module-sandbox-preload')
					or  p.message('template-sandbox-preload'),
			}, 'https'),
			frame:preprocess('{{lc:{{int:create}}}}')
		)
		local mirrorPreload = p.message('mirror-link-preload')
		if templateTitle.namespace == 828 then
			mirrorPreload = templateTitle.prefixedText
		end
		local mirrorLink = p.makeUrlLink(
			sandboxTitle:fullUrl({
				action  = 'edit',
				redlink = '1',
				preload = mirrorPreload,
				summary = i18n:msg('mirror-edit-summary', p.makeWikilink(templatePage)),
			}, 'https'),
			i18n:msg('mirror-link-display')
		)

		sandboxLinks:node(p.makeToolbar(sandboxCreateLink, mirrorLink))
	else
		sandboxLinks = nil
	end

	if checkExists(testcasesTitle.prefixedText) then
		testcasesLinks:wikitext(
			p.makeUrlWikilink(testcasesTitle.prefixedText, i18n:msg('testcases-link-display')),
			' '
		)

		local testcasesEditLink = p.makeUrlLink(
			testcasesTitle:fullUrl({ action = 'edit' }, 'https'),
			frame:preprocess('{{lc:{{int:edit}}}}')
		)

		if env.subjectSpace == 828 then
			local testcasesRunLink
			local testcasesRunLinkDisplay = p.message('sandbox-notice-testcases-run-link-display')
			local runTitle = testcasesTitle.talkPageTitle
			if checkExists(runTitle.prefixedText) then
				testcasesRunLink = p.makeUrlWikilink(runTitle.prefixedText, testcasesRunLinkDisplay)
			else
				testcasesRunLink = p.makeUrlLink(runTitle:fullUrl({
					action = 'edit',
					redlink = '1',
					preload = p.message('module-testcases-run-preload'),
				}), testcasesRunLinkDisplay)
			end
			testcasesLinks:node(p.makeToolbar(testcasesEditLink, testcasesRunLink))
		else
			testcasesLinks:node(p.makeToolbar(testcasesEditLink))
		end
	else
		testcasesLinks:wikitext(
			i18n:msg('testcases-link-display'),
			' '
		)

		local testcasesCreateLink = p.makeUrlLink(
			testcasesTitle:fullUrl({
				action  = 'edit',
				redlink = '1',
				preload = subjectSpace == 828
					and p.message('module-testcases-preload')
					or  p.message('template-testcases-preload'),
			}, 'https'),
			frame:preprocess('{{lc:{{int:create}}}}')
		)

		testcasesLinks:node(p.makeToolbar(testcasesCreateLink))
	end

	local msgArgs = { tostring(testcasesLinks):gsub('%%', '%%%%') or '' }

	local messageName =
		subjectSpace == 828
			and 'experiment-blurb-module'
			or  'experiment-blurb-template'

	if sandboxLinks then
		table.insert(msgArgs, 1, tostring(sandboxLinks):gsub('%%', '%%%%') or '')
	else
		messageName = messageName .. '-nosandbox'
	end

	return i18n:msg{
		key  = messageName,
		args = msgArgs,
	}
end

--[[
-- Generates the text "Please add categories to the /doc subpage."
--
-- ; Messages:
-- : 'doc-link-display' --> '/doc'
-- : 'add-categories-blurb' --> 'Please add categories to the $1 subpage.'
--
-- @function p.makeCategoriesBlurb
-- @private
-- @param {table} args - a table of arguments passed by the user
-- @param {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string|nil}
--]]
function p.makeCategoriesBlurb(args, env)
	local docTitle = env.docTitle
	if not docTitle then
		return nil
	end

	local docPathLink = p.makeUrlWikilink(
		docTitle.prefixedText,
		p.message('doc-link-display')
	):gsub("%%", "%%%%")

	return i18n:msg('add-categories-blurb', docPathLink)
end

--[[
-- Generates the "Subpages of this template" link.
--
-- ; Messages:
-- : 'template-pagetype' --> 'template'
-- : 'module-pagetype' --> 'module'
-- : 'default-pagetype' --> 'page'
-- : 'subpages-link-display' --> 'Subpages of this $1'
--
-- @function p.makeSubpagesBlurb
-- @private
-- @param {table} args - a table of arguments passed by the user
-- @param {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string|nil}
--]]
function p.makeSubpagesBlurb(args, env)
	local subjectSpace = env.subjectSpace
	local templateTitle = env.templateTitle
	if not subjectSpace or not templateTitle then
		return nil
	end

	local namespaceName = p.resolveNamespace(subjectSpace)
	assert(namespaceName ~= 'file')

	return i18n:msg(
		p.resolveNamespace(subjectSpace) .. '-subpages-link',
		'Special:PrefixIndex/' .. templateTitle.prefixedText .. '/'
	)
end

---
-- Generates the blurb displayed when there is a print version of the template available.
--
-- ; Messages:
-- : 'print-link-display' --> '/Print'
-- : 'print-blurb' --> 'A [[Help:Books/for experts#Improving the book layout|print version]]'
--		.. ' of this template exists at $1.'
--		.. ' If you make a change to this template, please update the print version as well.'
-- : 'display-print-category' --> true
-- : 'print-category' --> 'Templates with print versions'
--
-- @function p.makePrintBlurb
-- @private
-- @param {table} args - a table of arguments passed by the user
-- @param {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string|nil}
function p.makePrintBlurb(args, env)
	local printTitle = env.printTitle
	if not printTitle then
		return nil
	end
	local ret
	if checkExists(printTitle.prefixedText) then
		local printLink = p.makeWikilink(printTitle.prefixedText, p.message('print-link-display'))
		ret = p.message('print-blurb', {printLink})
		local displayPrintCategory = p.message('display-print-category', nil, 'boolean')
		if displayPrintCategory then
			ret = ret .. p.makeCategoryLink(p.message('print-category'))
		end
	end
	return ret
end

-- --------------------------------------------------------------------------
-- Tracking categories
-- --------------------------------------------------------------------------

--[[
-- Check if {{T|documentation}} is transcluded on a /doc or /testcases page.
--
-- ; Messages:
-- : 'display-strange-usage-category' --> true
-- : 'doc-subpage' --> 'doc'
-- : 'testcases-subpage' --> 'testcases'
-- : 'strange-usage-category' --> 'Pages with strange ((documentation)) usage'
-- : 'nodoc-category-template' --> 'Templates with no documentation'
-- : 'nodoc-category-module' --> 'Modules with no documentation'
-- : 'nodoc-category-file' --> 'Files with no summary'
-- : 'nodoc-category-other' --> 'Pages with no documentation'
-- : 'baddoc-category-template' --> 'Templates with bad documentation'
-- : 'baddoc-category-module' --> 'Modules with bad documentation'
-- : 'baddoc-category-file' --> 'Files with bad summary'
-- : 'baddoc-category-other' --> 'Pages with bad documentation'
--
-- /testcases pages in the module namespace are not categorised, as they may have
-- {{T|documentation}} transcluded automatically.
--
-- @function p.addTrackingCategories
-- @private
-- @param {Environment} env - environment table containing title objects, etc., generated with p.getEnvironment
-- @return {string|nil}
--]]
function p.addTrackingCategories(env)
	local title = env.title
	local subjectSpace = env.subjectSpace
	if not title or not subjectSpace then
		return nil
	end
	local subpage = title.subpageText
	local ret = ''
	if p.message('display-strange-usage-category', nil, 'boolean')
		and (
			subpage == p.message('doc-subpage')
			or subjectSpace ~= 828 and subpage == p.message('testcases-subpage')
		)
	then
		ret = ret .. p.makeCategoryLink(p.message('strange-usage-category'))
	end
	local docStatus = env.docStatus
	if docStatus then
		ret = ret .. p.makeCategoryLink(p.message(docStatus .. '-category-' .. p.resolveNamespace(subjectSpace)))
	end
	if p.resolveNamespace(subjectSpace) ~= 'other' and subpage ~= p.message('sandbox-subpage') then
		ret = ret .. p.makeCategoryLink(p.resolveNamespace(subjectSpace, true))
	end
	return ret
end

-- --------------------------------------------------------------------------
-- Type category
-- --------------------------------------------------------------------------
--[[
-- Add type if in arguments
-- @function p.addTypeCategory
-- @private
-- @param {table} args - a table of arguments passed by the user
-- @return {string}
--]]
function p.addTypeCategory(args)
	if lib.isEmpty(args['type']) then return '' end
	local templateTypes = lib.split(args['type'], ';')
	local ret = ''
	if templateTypes then
		for _,Type in ipairs(templateTypes) do
			ret = ret .. p.makeCategoryLink(Type .. ' Templates')
		end
	end
	return ret
end

return p