Module:Documentation: Difference between revisions

From Modded Wiki
Jump to navigation Jump to search
Changed the categories "No/bad doc" and "Doc page" to not be added on user pages, as the former should only be changed by the owner (making the categoryuseless), and the latter is not very relevant to the category anyways
m 142 revisions imported
 
(117 intermediate revisions by 19 users not shown)
Line 1: Line 1:
local p = {}
local p = {}


local getType = function( namespace, page )
-- Load modules (language wikis exclusive)
-- ...
 
-- Customizable strings
local i18n = {
-- default settings, change when necessary
defaultDocPage = 'doc', -- documentation page suffix
defaultSandboxPage = 'sandbox', -- sandbox page suffix
defaultTestCasePage = 'testcases', -- testcases page suffix
defaultPreload = 'Template:Documentation/preload', -- page that stores qualified documentation page contents
defaultStyles = 'Module:Documentation/styles.css', -- stylesheet for this module when using TemplateStyles, remove or set to nil if your wiki not use this
 
-- format strings, should not be translated
commonInternalLink = '[[%s]]',
commonInternalLinkPipe = '[[%s|%s]]',
commonExternalLink = '[%s]',
commonExternalLinkWithName = '[%s %s]',
commonNamespacedPage = '%s:%s',
commonNamespacedPageWithSub = '%s:%s/%s',
 
-- namespace names, translate if your language prefers localized namespace name, although remain it untouched most likely not affect anything
namespaceCategory = 'Category',
namespaceSpecial = 'Special',
namespaceUser = 'User',
 
-- names of special pages, translate if your language prefers localized namespace name, although remain it untouched will still correctly linked to target page
specialPurge = 'Purge',
specialEdit = 'EditPage',
specialHistory = 'PageHistory',
 
-- translate following types if your language displays differ
pageType_template = 'template',
pageType_module = 'module',
pageType_widget = 'widget',
pageType_stylesheet = 'stylesheet',
pageType_script = 'script',
pageType_message = 'message',
 
-- modify them if your wiki use different style to displaying links
linkBar = '%s', -- format used for whole link bar
linkFormat = mw.text.nowiki( '[' ) .. '%s' .. mw.text.nowiki( ']' ), -- format used for each individual links
linkSeparator = ' ', -- separator between links
 
-- name of different type of links, change them if necessary
linkTextPurge = mw.getCurrentFrame():callParserFunction( 'int:smw_purge' ):lower(),
linkTextView = mw.getCurrentFrame():callParserFunction( 'int:view' ):lower(),
linkTextEdit = mw.getCurrentFrame():callParserFunction( 'int:edit' ):lower(),
linkTextHistory = mw.getCurrentFrame():callParserFunction( 'int:history_short' ):lower(),
linkTextCreate = mw.getCurrentFrame():callParserFunction( 'int:create' ):lower(),
 
-- strings used in p.create(): contents shown when using {{docc}} or {{subst:docc}}
createOutputFormat = '%s%s', -- overall format
createSplitDocPagePrompt = '\n<!-- Put categories/interwikis on the documentation page -->', -- this string is shown when a separate documentation page is created
createNoSubstCategory = 'Pages with templates requiring substitution', -- tracking category for using {{docc}} without substitution
 
-- strings used in p.docPage(): contents shown in documentation page
docPagePrompt = 'This is the documentation page. It %s transcluded into %s. See [[Template:Documentation]] for more information.', -- message shown as documentation header in documentation pages. Params: word used when page is a module or not; code page's type
docPagePromptWill = 'is', -- word used when code page is a module page
docPagePromptShould = 'should be', -- word used when code is not a module page
docPageBadDocPrompt = "<br>'''This %s's documentation needs improving or additional information.'''", -- additional message if a documentation page marked as baddoc
docPageCategory = 'Documentation pages', -- tracking category for documentation pages
 
-- strings used in p.page(): contents shown in code page
pageNoDocPrompt = "'''This %s has no documentation. If you know how to use this %s, please create it.'''", -- message shown when a separate documentation page is not exist, both parameters refers to page type
pageNoDocCategory = '%ss with no documentation', -- tracking category for pages without documentation, parameters refers to page type
pageNoDocCategoryDefault = 'Pages with no documentation', -- fallback tracking category for pages without documentation
pageBadDocPrompt = "'''This %s's documentation needs improving or additional information.'''\n", -- message shown when a documentation page marked as baddoc, both parameters refers to page type
pageBadDocCategory = '%ss with bad documentation', -- tracking category for pages marked as baddoc, parameters refers to page type
pageBadDocCategoryDefault = 'Pages with bad documentation', -- fallback tracking category for pages marked as baddoc
pageDocHeaderTitle = 'Documentation', -- message shown as the title of the documentation header
pageDocJumpToCode = 'Jump to code ↴', -- text of the link to jump to the code
pageDocHeaderBottom = 'The above documentation is transcluded from %s.', -- message shown as the bottom line of the documentation header
}
 
-- Customizable functions
local function pageCategoryHandler( category )
return mw.getCurrentFrame():expandTemplate{ title = 'translation category', args = { category, project = '0' } }
end
 
-- Load modules
local loadStyles = require( 'Module:TSLoader' ).call
 
local static = require( 'Module:Static' )
if not static.Documentation then
static.Documentation = {}
end
 
-- Internal functions
local function getType( namespace, page )
local pageType = 'template'
local pageType = 'template'
if namespace == 'Module' then
if namespace == 'Module' then
pageType = 'module'
pageType = 'module'
elseif page.fullText:gsub( '/doc$', '' ):find( '%.css$' ) then
elseif namespace == 'Widget' then
pageType = 'widget'
elseif page.fullText:gsub( '/' .. i18n.defaultDocPage .. '$', '' ):find( '%.css$' ) then
pageType = 'stylesheet'
pageType = 'stylesheet'
elseif page.fullText:gsub( '/doc$', '' ):find( '%.js$' ) then
elseif page.fullText:gsub( '/' .. i18n.defaultDocPage .. '$', '' ):find( '%.js$' ) then
pageType = 'script'
pageType = 'script'
elseif namespace == 'MediaWiki' then
elseif namespace == 'MediaWiki' then
pageType = 'message'
pageType = 'message'
end
end
return pageType
return pageType
end
end


-- Creating a documentation page or transclution through {{subst:doc}}
local function getDisplayType( pageType )
function p.create( f )
return i18n[ 'pageType_' .. pageType ] or i18n.pageType_template
end
 
-- Exported functions
function p.create( f ) -- Creating a documentation page or transclusion through {{subst:docc}}
local args = require( 'Module:ProcessArgs' ).norm()
local args = require( 'Module:ProcessArgs' ).norm()
local page = mw.title.getCurrentTitle()
local page = mw.title.getCurrentTitle()
local docPage = args.page or page.nsText .. ':' .. page.baseText .. '/doc'
local docPage = args.page or i18n.commonNamespacedPageWithSub:format( page.nsText, page.baseText, i18n.defaultDocPage )
 
local out
local out
if not args.content and tostring( page ) == docPage then
if not args.content and tostring( page ) == docPage then
out = f:preprocess( '{{subst:Template:Documentation/preload}}' )
local pageType = mw.ustring.lower( args.type or getType( page.nsText, page ) )
local pageTypeDisplay = getDisplayType( pageType )
out = f:preprocess( mw.title.new( i18n.defaultPreload ):getContent():gsub( '$1' , pageTypeDisplay ) )
else
else
local templateArgs = {}
local templateArgs = {}
for _, key in ipairs{ 'type', 'page', 'content' } do
for _, key in ipairs{ 'type', 'page', 'content', 'nodoc', 'baddoc' } do
local val = args[key]
local val = args[ key ]
if val then
if val then
if key == 'content' then val = '\n' .. val .. '\n' end
if key == 'content' then val = '\n' .. val .. '\n' end
Line 34: Line 129:
end
end
end
end
out = '{{documentation|' .. table.concat( templateArgs, '|' ) .. '}}'
out = '{{documentation|' .. table.concat( templateArgs, '|' ) .. '}}'
out = out:gsub( '|}}', '}}' )
out = out:gsub( '|}}', '}}' )
out = i18n.createOutputFormat:format( out, args.content and '' or i18n.createSplitDocPagePrompt )
if not args.content then
out = out .. '\n<!-- Put categories/interwiki on the documentation page -->'
end
end
end
 
if not mw.isSubsting() then
if not mw.isSubsting() then
out = f:preprocess( out )
out = f:preprocess( out )
if not args.nocat then
if not args.nocat then
out = out .. '[[Category:Pages with templates requiring substitution]]'
out = out .. i18n.commonInternalLink:format( i18n.commonNamespacedPage:format( i18n.namespaceCategory, i18n.createNoSubstCategory ) )
end
end
end
end
 
return out
return out
end
end


-- Header on the documentation page
function p.docPage( f ) -- Header on the documentation page
function p.docPage( f )
local args = require( 'Module:ProcessArgs' ).merge( true )
local args = require( 'Module:ProcessArgs' ).merge( true )
local badDoc = args.baddoc
local badDoc = args.baddoc
if f:callParserFunction( '#dplvar', '$doc noheader' ) == '1' then
 
if badDoc then
if badDoc then
f:callParserFunction( '#dplvar:set', '$doc bad', '1' )
static.Documentation.badDoc = '1'
end
return
end
end
 
local page = mw.title.getCurrentTitle()
local page = mw.title.getCurrentTitle()
local subpage = page.subpageText
if subpage == i18n.defaultSandboxPage or subpage == i18n.defaultTestCasePage then
page = page.basePageTitle
end
local docPage = mw.title.new( args.page or i18n.commonNamespacedPageWithSub:format( page.nsText, page.baseText, i18n.defaultDocPage ) )
if docPage ~= page then return end
local namespace = page.nsText
local namespace = page.nsText
local pageType = mw.ustring.lower( args.type or getType( namespace, page ) )
local pageType = mw.ustring.lower( args.type or getType( namespace, page ) )
local pageTypeDisplay = getDisplayType( pageType )
local body = mw.html.create( 'div' )
 
local body = mw.html.create( 'div' ):addClass( 'documentation' )
body
body
:css{
:addClass( badDoc and 'documentation-badDoc' or '' )
['margin-bottom'] = '0.8em',
padding = '0.8em 1em 0.7em',
['background-color'] = '#' .. ( badDoc and 'F9F2EA' or 'EAF4F9' ),
border = '1px solid #AAA'
}
:tag( 'div' )
:tag( 'div' )
:css( 'float', 'right' )
:attr( 'id', 'documentation-header-tools' )
:wikitext( '[[', page:fullUrl( 'action=purge' ), ' purge]]' )
:wikitext( i18n.linkBar:format( i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( i18n.commonNamespacedPageWithSub:format( i18n.namespaceSpecial, i18n.specialPurge, page.fullText ), i18n.linkTextPurge ) ) ) )
:done()
:done()
:wikitext(
:wikitext( i18n.docPagePrompt:format( pageType == 'module' and i18n.docPagePromptWill or i18n.docPagePromptShould, i18n.commonInternalLink:format( i18n.commonNamespacedPage:format( namespace, page.baseText ) ) ) )
'This is the documentation page, it ',
pageType == 'module' and 'will' or 'should',
' be transcluded into the main ', pageType, ' page. ',
'See [[Template:Documentation]] for more information'
)
if badDoc then
if badDoc then
body:wikitext( "<br>'''This ", pageType, "'s documentation needs improving or additional information.'''" )
body:wikitext( i18n.docPageBadDocPrompt:format( pageTypeDisplay ) )
end
end
if not ( args.nocat or namespace == 'User' ) then
if not ( args.nocat or namespace == i18n.namespaceUser ) then
body:wikitext( '[[Category:Documentation pages]]' )
body:wikitext( i18n.commonInternalLink:format( i18n.commonNamespacedPage:format( i18n.namespaceCategory, i18n.docPageCategory ) ) )
end
end
 
return body
return loadStyles( i18n.defaultStyles ) .. tostring( body )
end
end


-- Wrapper around the documentation on the main page
function p.page( f ) -- Wrapper around the documentation on the main page
function p.page( f )
-- mw.text.trim uses mw.ustring.gsub, which silently fails on large strings
local function trim( s )
return ( s:gsub( '^[\t\r\n\f ]+', '' ):gsub( '[\t\r\n\f ]+$', '' ) )
--return string.gsub( s, '^[\t\r\n\f ]*(.-)[\t\r\n\f ]*$', '%1' )
end
local args = require( 'Module:ProcessArgs' ).merge( true )
local args = require( 'Module:ProcessArgs' ).merge( true )
local page = mw.title.getCurrentTitle()
local page = mw.title.getCurrentTitle()
local subpage = page.subpageText
if subpage == i18n.defaultSandboxPage or subpage == i18n.defaultTestCasePage then
page = page.basePageTitle
end
local namespace = page.nsText
local namespace = page.nsText
local docText = mw.text.trim( args.content or '' )
local docText = trim( args.content or '' )
if docText == '' then docText = nil end
if docText == '' then docText = nil end
 
local docPage
local docPage
local noDoc
local noDoc
Line 109: Line 205:
docPage = page
docPage = page
else
else
docPage = mw.title.new( args.page or namespace .. ':' .. page.text .. '/doc' )
docPage = mw.title.new( args.page or i18n.commonNamespacedPageWithSub:format( namespace, page.text, i18n.defaultDocPage ) )
noDoc = args.nodoc or not docPage.exists
noDoc = args.nodoc or not docPage.exists
end
end
local badDoc = args.baddoc
local badDoc = args.baddoc
local pageType = mw.ustring.lower( args.type or getType( namespace, page ) )
local pageType = mw.ustring.lower( args.type or getType( namespace, page ) )
local pageTypeDisplay = getDisplayType( pageType )
 
if not docText and not noDoc then
if not docText and not noDoc then
f:callParserFunction( '#dplvar:set', '$doc noheader', '1' )
docText = trim( f:expandTemplate{ title = ':' .. docPage.fullText } )
docText = mw.text.trim( f:expandTemplate{ title = ':' .. docPage.fullText } )
if static.Documentation.badDoc and static.Documentation.badDoc == '1' then
if f:callParserFunction( '#dplvar', '$doc bad' ) == '1' then
badDoc = 1
badDoc = 1
end
end
 
if docText == '' then
if docText == '' then
docText = nil
docText = nil
Line 130: Line 226:
docText = '\n' .. docText .. '\n'
docText = '\n' .. docText .. '\n'
end
end
 
local action = 'edit'
local docClass = ''
local preload = ''
local colour = 'EAF4F9'
local message
local message
local category
local category
if noDoc then
if noDoc then
action = 'create'
docClass = 'documentation-noDoc'
preload = '&preload=Template:Documentation/preload'
message = i18n.pageNoDocPrompt:format( pageTypeDisplay, pageTypeDisplay )
colour = 'F9EAEA'
if not ( args.nocat or namespace == i18n.namespaceUser ) then
message = "'''This " .. pageType .. " has no documentation. " ..
category = i18n.pageNoDocCategory:format( pageTypeDisplay )
"If you know how to use this " .. pageType .. ", please create it.'''"
if not mw.title.new( i18n.commonNamespacedPage:format( i18n.namespaceCategory, category ) ).exists then
if not ( args.nocat or namespace == 'User' ) then
category = i18n.pageNoDocCategoryDefault
category = 'Category:' .. pageType .. 's with no documentation'
if not mw.title.new( category ).exists then
category = 'Category:Pages with no documentation'
end
end
end
end
elseif badDoc then
elseif badDoc then
colour = 'F9F2EA'
docClass = 'documentation-badDoc'
message = "'''This " .. pageType .. "'s documentation needs improving or additional information.'''\n"
message = i18n.pageBadDocPrompt:format( pageTypeDisplay )
if not ( args.nocat or namespace == 'User' ) then
if not ( args.nocat or namespace == i18n.namespaceUser ) then
category = 'Category:' .. pageType .. 's with bad documentation'
category = i18n.pageBadDocCategory:format( pageTypeDisplay )
if not mw.title.new( category ).exists then
if not mw.title.new( i18n.commonNamespacedPage:format( i18n.namespaceCategory, category ) ).exists then
category = 'Category:Pages with bad documentation'
category = i18n.pageBadDocCategoryDefault
end
end
end
end
end
end
 
local links = {
-- Generates the link bar
'[' .. docPage:fullUrl( 'action=edit' .. preload ) .. ' ' .. action .. ']',
local links = mw.html.create( 'span' )
'[' .. page:fullUrl( 'action=purge' ) .. ' purge]'
:attr( 'id', 'documentation-header-tools' )
}
 
if not noDoc and page ~= docPage then
local linkList = {}
table.insert( links, 1, '[[' .. docPage.fullText .. '|view]]' )
if not noDoc then
if page ~= docPage then
table.insert( linkList, i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( docPage.fullText, i18n.linkTextView ) ) )
end
table.insert( linkList, i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( i18n.commonNamespacedPageWithSub:format( i18n.namespaceSpecial, i18n.specialEdit, docPage.fullText ), i18n.linkTextEdit ) ) )
table.insert( linkList, i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( i18n.commonNamespacedPageWithSub:format( i18n.namespaceSpecial, i18n.specialHistory, docPage.fullText ), i18n.linkTextHistory ) ) )
else
table.insert( linkList, i18n.linkFormat:format( i18n.commonExternalLinkWithName:format( docPage:canonicalUrl{ action = 'edit', preload = i18n.defaultPreload, preloadparams = pageTypeDisplay }, i18n.linkTextCreate ) ) )
end
end
links = mw.html.create( 'span' )
table.insert( linkList, i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( i18n.commonNamespacedPageWithSub:format( i18n.namespaceSpecial, i18n.specialPurge, docPage.fullText ), i18n.linkTextPurge ) ) )
:css( 'float', 'right' )
links:wikitext( i18n.linkBar:format( table.concat( linkList, i18n.linkSeparator ) ) )
:wikitext( mw.text.nowiki( '[' ), table.concat( links, ' | ' ), mw.text.nowiki( ']' ) )
 
local body = mw.html.create( 'div' ):addClass( 'documentation' )
local body = mw.html.create( 'div' )
body
body:css{
:addClass( docClass )
['background-color'] = '#' .. colour,
 
border = '1px solid #AAA',
padding = '0.8em 1em 0.7em',
['margin-top'] = '1em',
clear = 'both'
}
local header = mw.html.create( 'div' )
local header = mw.html.create( 'div' )
:css{
:addClass( 'documentation-header-top' )
margin = '-0.8em -1em 0.8em',
 
padding = '0.8em 1em 0.7em',
['background-color'] = '#EAF4F9',
['border-bottom'] = 'inherit'
}
header
header
:node( links )
:node( links )
:tag( 'span' )
:tag( 'span' )
:css{
:addClass( 'documentation-header-title' )
['font-weight'] = 'bold',
:wikitext( i18n.pageDocHeaderTitle )
['font-size'] = '130%',
 
['margin-right'] = '1em',
local codePages = {
['line-height'] = '1'
module = true,
}
stylesheet = true,
:wikitext( 'Documentation' )
script = true,
}
if not noDoc and pageType ~= 'template' and pageType ~= 'message' then
if not noDoc and codePages[ pageType ] then
header
header
:tag( 'span' )
:tag( 'span' )
:css( 'white-space', 'nowrap' )
:attr( 'id', 'documentation-jump-to-code' )
:wikitext( '[[#the-code|Jump to code ↴]]' )
:wikitext( i18n.commonInternalLinkPipe:format( '#the-code', i18n.pageDocJumpToCode ) )
end
end
 
body
body
:node( header ):done()
:node( header ):done()
:wikitext( message )
:wikitext( message )
:wikitext( docText )
:wikitext( docText )
 
if not noDoc and page ~= docPage then
if not noDoc and page ~= docPage then
body
body
:tag( 'div' )
:tag( 'div' )
:css{
:addClass( 'documentation-header-bottom' )
margin = '0.7em -1em -0.7em',
['background-color'] = '#EAF4F9',
['border-top'] = 'inherit',
padding = '0.8em 1em 0.7em',
clear = 'both'
}
:node( links )
:node( links )
:wikitext( 'The above documentation is transcluded from [[', docPage.fullText, ']].' )
:wikitext( i18n.pageDocHeaderBottom:format( i18n.commonInternalLink:format( docPage.fullText ) ) )
end
end
 
if category then
if category then
body:wikitext( '[[', category, ']]' )
body:wikitext( pageCategoryHandler( category ) )
end
end
 
local anchor = ''
local anchor = ''
if not noDoc and pageType ~= 'template' and pageType ~= 'message' then
if not noDoc and pageType ~= 'template' and pageType ~= 'message' then
anchor = mw.html.create( 'div' ):attr( 'id', 'the-code' )
anchor = mw.html.create( 'div' ):attr( 'id', 'the-code' )
end
end
 
return tostring( body ) .. tostring( anchor )
return loadStyles( i18n.defaultStyles ) .. tostring( body ) .. tostring( anchor )
end
end


return p
return p

Latest revision as of 07:52, 4 July 2024

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

local p = {}

-- Load modules (language wikis exclusive)
-- ...

-- Customizable strings
local i18n = {
	-- default settings, change when necessary
	defaultDocPage = 'doc',	-- documentation page suffix
	defaultSandboxPage = 'sandbox',	-- sandbox page suffix
	defaultTestCasePage = 'testcases',	-- testcases page suffix
	defaultPreload = 'Template:Documentation/preload',	-- page that stores qualified documentation page contents
	defaultStyles = 'Module:Documentation/styles.css',	-- stylesheet for this module when using TemplateStyles, remove or set to nil if your wiki not use this

	-- format strings, should not be translated
	commonInternalLink = '[[%s]]',
	commonInternalLinkPipe = '[[%s|%s]]',
	commonExternalLink = '[%s]',
	commonExternalLinkWithName = '[%s %s]',
	commonNamespacedPage = '%s:%s',
	commonNamespacedPageWithSub = '%s:%s/%s',

	-- namespace names, translate if your language prefers localized namespace name, although remain it untouched most likely not affect anything
	namespaceCategory = 'Category',
	namespaceSpecial = 'Special',
	namespaceUser = 'User',

	-- names of special pages, translate if your language prefers localized namespace name, although remain it untouched will still correctly linked to target page
	specialPurge = 'Purge',
	specialEdit = 'EditPage',
	specialHistory = 'PageHistory',

	-- translate following types if your language displays differ
	pageType_template = 'template',
	pageType_module = 'module',
	pageType_widget = 'widget',
	pageType_stylesheet = 'stylesheet',
	pageType_script = 'script',
	pageType_message = 'message',

	-- modify them if your wiki use different style to displaying links
	linkBar = '%s',	-- format used for whole link bar
	linkFormat = mw.text.nowiki( '[' ) .. '%s' .. mw.text.nowiki( ']' ),	-- format used for each individual links
	linkSeparator = ' ',	-- separator between links

	-- name of different type of links, change them if necessary
	linkTextPurge = mw.getCurrentFrame():callParserFunction( 'int:smw_purge' ):lower(),
	linkTextView = mw.getCurrentFrame():callParserFunction( 'int:view' ):lower(),
	linkTextEdit = mw.getCurrentFrame():callParserFunction( 'int:edit' ):lower(),
	linkTextHistory = mw.getCurrentFrame():callParserFunction( 'int:history_short' ):lower(),
	linkTextCreate = mw.getCurrentFrame():callParserFunction( 'int:create' ):lower(),

	-- strings used in p.create(): contents shown when using {{docc}} or {{subst:docc}}
	createOutputFormat = '%s%s',	-- overall format
	createSplitDocPagePrompt = '\n<!-- Put categories/interwikis on the documentation page -->',	-- this string is shown when a separate documentation page is created
	createNoSubstCategory = 'Pages with templates requiring substitution',	-- tracking category for using {{docc}} without substitution

	-- strings used in p.docPage(): contents shown in documentation page
	docPagePrompt = 'This is the documentation page. It %s transcluded into %s. See [[Template:Documentation]] for more information.',	-- message shown as documentation header in documentation pages. Params: word used when page is a module or not; code page's type
	docPagePromptWill = 'is',	-- word used when code page is a module page
	docPagePromptShould = 'should be',	-- word used when code is not a module page
	docPageBadDocPrompt = "<br>'''This %s's documentation needs improving or additional information.'''",	-- additional message if a documentation page marked as baddoc
	docPageCategory = 'Documentation pages',	-- tracking category for documentation pages

	-- strings used in p.page(): contents shown in code page
	pageNoDocPrompt = "'''This %s has no documentation. If you know how to use this %s, please create it.'''",	-- message shown when a separate documentation page is not exist, both parameters refers to page type
	pageNoDocCategory = '%ss with no documentation',	-- tracking category for pages without documentation, parameters refers to page type
	pageNoDocCategoryDefault = 'Pages with no documentation',	-- fallback tracking category for pages without documentation
	pageBadDocPrompt = "'''This %s's documentation needs improving or additional information.'''\n",	-- message shown when a documentation page marked as baddoc, both parameters refers to page type
	pageBadDocCategory = '%ss with bad documentation',	-- tracking category for pages marked as baddoc, parameters refers to page type
	pageBadDocCategoryDefault = 'Pages with bad documentation',	-- fallback tracking category for pages marked as baddoc
	pageDocHeaderTitle = 'Documentation',	-- message shown as the title of the documentation header
	pageDocJumpToCode = 'Jump to code ↴',	-- text of the link to jump to the code
	pageDocHeaderBottom = 'The above documentation is transcluded from %s.',	-- message shown as the bottom line of the documentation header
}

-- Customizable functions
local function pageCategoryHandler( category )
	return mw.getCurrentFrame():expandTemplate{ title = 'translation category', args = { category, project = '0' } }
end

-- Load modules
local loadStyles = require( 'Module:TSLoader' ).call

local static = require( 'Module:Static' )
if not static.Documentation then
	static.Documentation = {}
end

-- Internal functions
local function getType( namespace, page )
	local pageType = 'template'
	if namespace == 'Module' then
		pageType = 'module'
	elseif namespace == 'Widget' then
		pageType = 'widget'
	elseif page.fullText:gsub( '/' .. i18n.defaultDocPage .. '$', '' ):find( '%.css$' ) then
		pageType = 'stylesheet'
	elseif page.fullText:gsub( '/' .. i18n.defaultDocPage .. '$', '' ):find( '%.js$' ) then
		pageType = 'script'
	elseif namespace == 'MediaWiki' then
		pageType = 'message'
	end
	return pageType
end

local function getDisplayType( pageType )
	return i18n[ 'pageType_' .. pageType ] or i18n.pageType_template
end

-- Exported functions
function p.create( f )	-- Creating a documentation page or transclusion through {{subst:docc}}
	local args = require( 'Module:ProcessArgs' ).norm()
	local page = mw.title.getCurrentTitle()
	local docPage = args.page or i18n.commonNamespacedPageWithSub:format( page.nsText, page.baseText, i18n.defaultDocPage )

	local out
	if not args.content and tostring( page ) == docPage then
		local pageType = mw.ustring.lower( args.type or getType( page.nsText, page ) )
		local pageTypeDisplay = getDisplayType( pageType )
		out = f:preprocess( mw.title.new( i18n.defaultPreload ):getContent():gsub( '$1' , pageTypeDisplay ) )
	else
		local templateArgs = {}
		for _, key in ipairs{ 'type', 'page', 'content', 'nodoc', 'baddoc' } do
			local val = args[ key ]
			if val then
				if key == 'content' then val = '\n' .. val .. '\n' end
				table.insert( templateArgs, key .. '=' .. val )
			end
		end
		out = '{{documentation|' .. table.concat( templateArgs, '|' ) .. '}}'
		out = out:gsub( '|}}', '}}' )
		out = i18n.createOutputFormat:format( out, args.content and '' or i18n.createSplitDocPagePrompt )
	end

	if not mw.isSubsting() then
		out = f:preprocess( out )
		if not args.nocat then
			out = out .. i18n.commonInternalLink:format( i18n.commonNamespacedPage:format( i18n.namespaceCategory, i18n.createNoSubstCategory ) )
		end
	end

	return out
end

function p.docPage( f )	-- Header on the documentation page
	local args = require( 'Module:ProcessArgs' ).merge( true )
	local badDoc = args.baddoc

	if badDoc then
		static.Documentation.badDoc = '1'
	end

	local page = mw.title.getCurrentTitle()

	local subpage = page.subpageText
	if subpage == i18n.defaultSandboxPage or subpage == i18n.defaultTestCasePage then
		page = page.basePageTitle
	end

	local docPage = mw.title.new( args.page or i18n.commonNamespacedPageWithSub:format( page.nsText, page.baseText, i18n.defaultDocPage ) )
	if docPage ~= page then return end

	local namespace = page.nsText
	local pageType = mw.ustring.lower( args.type or getType( namespace, page ) )
	local pageTypeDisplay = getDisplayType( pageType )

	local body = mw.html.create( 'div' ):addClass( 'documentation' )
	body
		:addClass( badDoc and 'documentation-badDoc' or '' )
		:tag( 'div' )
			:attr( 'id', 'documentation-header-tools' )
			:wikitext( i18n.linkBar:format( i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( i18n.commonNamespacedPageWithSub:format( i18n.namespaceSpecial, i18n.specialPurge, page.fullText ), i18n.linkTextPurge ) ) ) )
		:done()
		:wikitext( i18n.docPagePrompt:format( pageType == 'module' and i18n.docPagePromptWill or i18n.docPagePromptShould, i18n.commonInternalLink:format( i18n.commonNamespacedPage:format( namespace, page.baseText ) ) ) )
	if badDoc then
		body:wikitext( i18n.docPageBadDocPrompt:format( pageTypeDisplay ) )
	end
	if not ( args.nocat or namespace == i18n.namespaceUser ) then
		body:wikitext( i18n.commonInternalLink:format( i18n.commonNamespacedPage:format( i18n.namespaceCategory, i18n.docPageCategory ) ) )
	end

	return loadStyles( i18n.defaultStyles ) .. tostring( body )
end

function p.page( f )	-- Wrapper around the documentation on the main page
	-- mw.text.trim uses mw.ustring.gsub, which silently fails on large strings
	local function trim( s )
		return ( s:gsub( '^[\t\r\n\f ]+', '' ):gsub( '[\t\r\n\f ]+$', '' ) )
		--return string.gsub( s, '^[\t\r\n\f ]*(.-)[\t\r\n\f ]*$', '%1' )
	end
	local args = require( 'Module:ProcessArgs' ).merge( true )
	local page = mw.title.getCurrentTitle()
	local subpage = page.subpageText
	if subpage == i18n.defaultSandboxPage or subpage == i18n.defaultTestCasePage then
		page = page.basePageTitle
	end
	local namespace = page.nsText
	local docText = trim( args.content or '' )
	if docText == '' then docText = nil end

	local docPage
	local noDoc
	if docText then
		docPage = page
	else
		docPage = mw.title.new( args.page or i18n.commonNamespacedPageWithSub:format( namespace, page.text, i18n.defaultDocPage ) )
		noDoc = args.nodoc or not docPage.exists
	end
	local badDoc = args.baddoc
	local pageType = mw.ustring.lower( args.type or getType( namespace, page ) )
	local pageTypeDisplay = getDisplayType( pageType )

	if not docText and not noDoc then
		docText = trim( f:expandTemplate{ title = ':' .. docPage.fullText } )
		if static.Documentation.badDoc and static.Documentation.badDoc == '1' then
			badDoc = 1
		end

		if docText == '' then
			docText = nil
			noDoc = 1
		end
	end
	if docText then
		docText = '\n' .. docText .. '\n'
	end

	local docClass = ''
	local message
	local category
	if noDoc then
		docClass = 'documentation-noDoc'
		message = i18n.pageNoDocPrompt:format( pageTypeDisplay, pageTypeDisplay )
		if not ( args.nocat or namespace == i18n.namespaceUser ) then
			category = i18n.pageNoDocCategory:format( pageTypeDisplay )
			if not mw.title.new( i18n.commonNamespacedPage:format( i18n.namespaceCategory, category ) ).exists then
				category = i18n.pageNoDocCategoryDefault
			end
		end
	elseif badDoc then
		docClass = 'documentation-badDoc'
		message = i18n.pageBadDocPrompt:format( pageTypeDisplay )
		if not ( args.nocat or namespace == i18n.namespaceUser ) then
			category = i18n.pageBadDocCategory:format( pageTypeDisplay )
			if not mw.title.new( i18n.commonNamespacedPage:format( i18n.namespaceCategory, category ) ).exists then
				category = i18n.pageBadDocCategoryDefault
			end
		end
	end

	-- Generates the link bar
	local links = mw.html.create( 'span' )
		:attr( 'id', 'documentation-header-tools' )

	local linkList = {}
	if not noDoc then
		if page ~= docPage then
			table.insert( linkList, i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( docPage.fullText, i18n.linkTextView ) ) )
		end
		table.insert( linkList, i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( i18n.commonNamespacedPageWithSub:format( i18n.namespaceSpecial, i18n.specialEdit, docPage.fullText ), i18n.linkTextEdit ) ) )
		table.insert( linkList, i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( i18n.commonNamespacedPageWithSub:format( i18n.namespaceSpecial, i18n.specialHistory, docPage.fullText ), i18n.linkTextHistory ) ) )
	else
		table.insert( linkList, i18n.linkFormat:format( i18n.commonExternalLinkWithName:format( docPage:canonicalUrl{ action = 'edit', preload = i18n.defaultPreload, preloadparams = pageTypeDisplay }, i18n.linkTextCreate ) ) )
	end
	table.insert( linkList, i18n.linkFormat:format( i18n.commonInternalLinkPipe:format( i18n.commonNamespacedPageWithSub:format( i18n.namespaceSpecial, i18n.specialPurge, docPage.fullText ), i18n.linkTextPurge ) ) )
	links:wikitext( i18n.linkBar:format( table.concat( linkList, i18n.linkSeparator ) ) )

	local body = mw.html.create( 'div' ):addClass( 'documentation' )
	body
		:addClass( docClass )

	local header = mw.html.create( 'div' )
		:addClass( 'documentation-header-top' )

	header
		:node( links )
		:tag( 'span' )
			:addClass( 'documentation-header-title' )
			:wikitext( i18n.pageDocHeaderTitle )

	local codePages = {
		module = true,
		stylesheet = true,
		script = true,
	}
	if not noDoc and codePages[ pageType ] then
		header
			:tag( 'span' )
				:attr( 'id', 'documentation-jump-to-code' )
				:wikitext( i18n.commonInternalLinkPipe:format( '#the-code', i18n.pageDocJumpToCode ) )
	end

	body
		:node( header ):done()
		:wikitext( message )
		:wikitext( docText )

	if not noDoc and page ~= docPage then
		body
			:tag( 'div' )
				:addClass( 'documentation-header-bottom' )
				:node( links )
				:wikitext( i18n.pageDocHeaderBottom:format( i18n.commonInternalLink:format( docPage.fullText ) ) )
	end

	if category then
		body:wikitext( pageCategoryHandler( category ) )
	end

	local anchor = ''
	if not noDoc and pageType ~= 'template' and pageType ~= 'message' then
		anchor = mw.html.create( 'div' ):attr( 'id', 'the-code' )
	end

	return loadStyles( i18n.defaultStyles ) .. tostring( body ) .. tostring( anchor )
end

return p