มอดูล:ป้ายโครงการวิกิ

require('strict')
local p = {}
local sandbox-- = '/sandbox'
local cfg = mw.loadData('มอดูล:ป้ายโครงการวิกิ/config' .. (sandbox or ))
local args_module = require('Module:Arguments')
local mbox = require('Module:Message box').main
local yesno = require('Module:Yesno')
local frame = mw.getCurrentFrame()
local lang = mw.getLanguage(cfg.language)
local current_title = mw.title.getCurrentTitle()
local parameter_format = function(parameter, value)
	return frame:expandTemplate{title='para', args={parameter, value or }}
end
local wikilink = function(link, display)
	if link then
		return display and ..display.. or ..link..
	else
		return display or 
	end
end
local image = function(image_name, size, alt, position)
	return image_name and 'alt=' .. alt or )
		.. 
end
local if_exists = function(target, fallback) -- function to add wikilink if target exists
	local title = mw.title.new(target)
	if title and title.exists then
		return wikilink(target)
	else
		return fallback or target
	end
end
local isarticle = function(class)
	local article = true
	for _,v in ipairs(cfg.quality.non_article_classes) do
		if class==v then -- class matches one of the non-article classes
			article = false
			break
		end
	end
	return article
end
---------------------------
-- Importance mask --------
---------------------------
local importance_mask = function(raw_importance, class, scale, banner_name)
	local importance
	if scale=='inline' then -- pass importance without change
		importance = raw_importance
	elseif scale=='subpage' then
		local custom_mask = banner_name:subPageTitle('importance')
		if custom_mask.exists and #custom_mask:getContent()>1 then -- pass to custom importance mask
			importance = mw.text.trim(frame:expandTemplate{
				title = custom_mask.prefixedText,
				args = {importance=raw_importance or '¬', class=class}
			})
		end
	else
		importance = frame:expandTemplate{
			title = 'Template:Importance mask',
			args = {raw_importance or '¬', class=class}}
	end
	if importance=='¬' then
		importance = nil
	end
	return importance
end
---------------------------
-- Quality class mask -----
---------------------------
p.readarticleclass = function(options, page) -- used by _main and also Module:Banner shell
	page = page or current_title.prefixedText
	local get_parameter_value = require('Module:Template parameter value').getValue
	local success, result = get_parameter_value(page, cfg.WPBS_redirects, 'class', options)
	return success and result
	-- returns FALSE if banner shell template does not exist on page
	-- returns BLANK if class parameter is not defined or is defined blank
	-- otherwise returns class parameter
end
p.class_mask = function(class, title, FQS)
	local resolveFQSgrade = function(class)
		return FQS and lang:ucfirst(class) or 'NA'
	end
	local pagetype = require('Module:Pagetype')._main({
		page = title.prefixedText,
		dab = 'dab'
	})
	local out =  -- default to unassessed
	title = title or mw.title.getCurrentTitle()
	local ns = title.namespace
	class = class:match('^%s*(.-)%s*$'):lower()
	if pagetype=='redirect' then
		out = resolveFQSgrade('redirect')
	elseif ns==1 then -- Talk
		if pagetype=='dab' then
			out = resolveFQSgrade('disambig')
		else
			if class=='start' or class=='stub' then -- Ucfirst
				out = lang:ucfirst(class)
			elseif class=='b' or class=='c' or class=='fa' or class=='fl' or class=='a' or class=='ga' then -- Upper-case
				out = class:upper()
			elseif class=='list' or class=='sia' or class=='si' or class=='sl' then-- List
				out = 'List'
			elseif class=='dab' or class=='disambig' or class=='disambiguation' or class=='disamb' then
				out = resolveFQSgrade('disambig')
			end
		end
	elseif ns==7 or ns==711 then -- File talk
		if class=='fm' and FQS then
			out = 'FM'
		else
			out = resolveFQSgrade('file')
		end
	elseif ns==15 then -- Category talk
		out = resolveFQSgrade('category')
	elseif ns==101 then -- Portal talk
		out = resolveFQSgrade('portal')
	elseif ns==11 or ns==829 then -- Template talk
		out = resolveFQSgrade('template')
	elseif ns==5 then -- Wikipedia talk
		out = resolveFQSgrade('project')
	elseif ns==119 then -- Draft talk
		out = resolveFQSgrade('draft')
	else
		out = 'NA'
	end
	return out
end
local page_assessment = function(project, class, importance) -- add PageAssessments parser function
	local assessment = table.concat({project, class or , importance or },'|')
	frame:preprocess()
end
local bubble = function(text, colour, conflict)
	local out = mw.html.create('span')
		:addClass('wpb-header-bubbles')
		:css('background', colour)
		:css('border', conflict and cfg.quality.conflict.border or (cfg.quality.border..' '..colour))
		:wikitext(text)
	return tostring(out)
end
p._main = function(args, raw_args, demo, banner_name)
---------------------------
-- Initialise parameters --
---------------------------
local project = args.PROJECT or 'PROJECT'
local project_name = args.PROJECT_NAME or 'WikiProject ' .. project
local project_link = mw.title.new(args.PROJECT_LINK or 'Wikipedia:' .. project_name)
local pagetype = demo and not args.demo_page and 'article' or require('Module:Pagetype')._main({
	page = args.demo_page
})
local rows, nested_ratings, task_forces, notes, categories, taskforce_categories = {}, {}, {}, {}, {}, {}
local add_category = function(category, key)
	if category and category~='none' then
		table.insert(categories, {category = category, key = key})
	end
end
local parse_text = function(text)
	return text and text:gsub('_PAGETYPE_', pagetype)
end
for arg_name, arg_value in pairs(args) do
	local tf_match = mw.ustring.match(arg_name,'^tf (%d+)$')
	local note_match = mw.ustring.match(arg_name,'^note (%d+)$')
	if tf_match and yesno(arg_value, true) then
		table.insert(task_forces, tf_match)
	elseif note_match and yesno(arg_value, true) then
		table.insert(notes, note_match)
	else
		local tf, cat = mw.ustring.match(arg_name,'^tf (%d+) cat (%d+)$')
		if tf and yesno(arg_value, true) then
			if not taskforce_categoriestf then -- initialise table
				taskforce_categoriestf = {}
			end
			table.insert(taskforce_categoriestf, cat)
		end
	end
end
table.sort(task_forces, function (x, y) return tonumber(x) '
	)
	warning = warning .. mbox('ombox', {
		image = '40px',
		type = cfg.subst_warning.type_,
		text = text,
	}) .. cfg.subst_warning.categories
end
---------------------------
-- Primary image/text -----
---------------------------
local assessment_cat = args.ASSESSMENT_CAT or project .. ' articles'
local primary_image = function(image_name, size)
	local cell = mw.html.create('td')
	if image_name and image_name~= then
		cell:addClass('mbox-image wpb-image')
		:wikitext(image(image_name, size, cfg.image.alt))
	else
		cell:addClass('mbox-empty-cell')
	end
	cell:done()
	return cell
end
local portal = args.PORTAL
local portal_box = portal and frame:expandTemplate{title='Portal', args={portal}} or 
local main_text = portal_box .. (parse_text(args.MAIN_TEXT) or cfg.main_text:format(
	pagetype,
	project_link.prefixedText,
	project_name,
	args.MAIN_ARTICLE and if_exists(args.MAIN_ARTICLE) or if_exists(project, project .. ' articles'),
	project_link.talkPageTitle.prefixedText
))
local image_left_size = args.IMAGE_LEFT_SIZE or cfg.image.default_size
local primary_row = mw.html.create('tr')
	:node(primary_image(args.IMAGE_LEFT, image_left_size))
	:tag('td')
		:addClass('mbox-text')
		:wikitext(main_text)
		:tag('span')
			:addClass('metadata wpb-metadata')
			:tag('span'):addClass('wpb-project'):wikitext(project):done()
			:tag('span'):addClass('wpb-project_link'):wikitext(project_link.prefixedText):done()
			:tag('span'):addClass('wpb-banner_name'):wikitext(banner_name.prefixedText):done()
			:tag('span'):addClass('wpb-assessment_cat'):wikitext(assessment_cat):done()
		:done()
	:done()	
	:node(primary_image(args.IMAGE_RIGHT, args.IMAGE_RIGHT_SIZE or cfg.image.default_size))
done()
table.insert(rows, primary_row)
---------------------------
-- Quality assessment -----
---------------------------
local assessment_link = args.ASSESSMENT_LINK
if not assessment_link then
	local fallback = mw.title.new(project_link.prefixedText .. '/Assessment')
	assessment_link = fallback.exists and fallback.prefixedText
elseif assessment_link=='no' then
	assessment_link = nil
end
local check_exists = function(class, assessment_cat) -- check if category exists and is not blank
	if not isarticle(class) then
		local cat =  mw.title.new('Category:' .. class .. '-Class' .. ' ' .. assessment_cat)
		return (cat.exists and #cat:getContent()>0) and class or 'NA' -- automatically use NA for non-article pages if category does not exist
	else
		return class
	end
end
local class = raw_args.class
if class then -- banner gives quality ratings
	local title = args.demo_page and mw.title.new(args.demo_page) or current_title
	local article_class = p.readarticleclass({ignore_subtemplates=true}, title.prefixedText)
	if not article_class then
		add_category('WikiProject banners without banner shells')
	end
	article_class = article_class and p.class_mask(article_class, title, false)
	local show_quality, conflict = true, false
	if args.QUALITY_CRITERIA=='custom' then -- project has opted out of standard assessment scale and uses a custom mask
		local custom_mask = banner_name:subPageTitle('class')
		if custom_mask.exists and #custom_mask:getContent()>1 then
			raw_args.demo_page = args.demo_page -- send demo_page to custom mask
			class = mw.text.trim(frame:expandTemplate{
				title = custom_mask.prefixedText,
				args = raw_args
			})
			if class== and article_class and article_class~= then -- if unassessed and article class exists, check if it can be inherited
				local new_arg_table = {}
				for arg, val in pairs(raw_args) do -- construct new argument table to send to custom mask
					new_arg_tablearg = val
				end
				new_arg_table.class = article_class -- replace class with inherited class
				local article_class_normalised = mw.text.trim(frame:expandTemplate{
					title = custom_mask.prefixedText,
					args = new_arg_table
				})
				if article_class_normalised and article_class_normalised~= then
					class = article_class_normalised -- inherit class from article_class normalised by custom mask
				else
					article_class = nil -- effectively no article_class for this banner
				end
			end
		end
	else
		class = p.class_mask(class, title, true)
	end
	if article_class then -- banner shell exists
		if article_class== then -- no article class defined
			if class== then -- local class also does not exist, check whether any other class parameters are defined inside the shell
				local classparam = p.readarticleclass({ignore_blank=true, only_subtemplates=true}, title.prefixedText)
				if classparam== then -- no class parameters defined, display as globally unassessed
					show_quality = false -- hide quality class in project banner
				end
			else
				add_category('Pages using WikiProject banner shell without a project-independent quality rating')
			end
		elseif class== or class==article_class then -- local class matches article class or is blank
			show_quality = false -- hide quality class in project banner
			class = article_class
			if raw_args.class~= and args.QUALITY_CRITERIA~='custom' then
				add_category('WikiProject banners with redundant class parameter')
			end
		elseif (article_class=='NA') and not isarticle(class) then -- article class and local class are both non-article classes
			show_quality = false
		else -- article class exists and differs from local class
			if args.QUALITY_CRITERIA~='custom' then
				conflict = true
				add_category(cfg.quality.conflict.category)
			end
		end
	end
	if not isarticle(class) then
		local cat =  mw.title.new(cfg.quality.assessment_category:format(class, assessment_cat))
		if not (cat.exists and #cat:getContent()>0) then --check if category exists and is not blank
			class = 'NA' -- automatically use NA for non-article pages if category does not exist
		end
	end
	local category = (class== and 'Unassessed' or class..'-Class') .. ' ' .. assessment_cat
	if show_quality then -- quality rating shown in banner
		local class_module = require('Module:Class')._class
		local rating
		if pagetype=='article' then
			rating = class== and cfg.quality.not_yet or cfg.quality.rated:format(class)
		else
			rating = cfg.quality.not_required
		end
		local scale = args.QUALITY_CRITERIA=='custom'
			and assessment_link
			and cfg.quality.project_scale:format(wikilink(assessment_link..'#'..lang:ucfirst(cfg.quality.name), cfg.quality.name))
			or cfg.quality.default_scale
		local quality_rating = conflict and cfg.quality.conflict.text or cfg.quality.rating:format(pagetype, rating, scale)
		local colour = cfg.quality.colourclass or cfg.quality.colour.default
		local class_row =  mw.html.create('tr')
			:tag('td')
				:addClass('assess'):addClass('assess-' .. class)
				:css('background', colour):wikitext(wikilink(':Category:' .. category, class== and '???' or class))
				:css('border', conflict and cfg.quality.conflict.border or (cfg.quality.border..' '..colour))
			:done()
			:tag('td'):addClass('mbox-text'):attr('colspan', '2'):wikitext(quality_rating)
		:allDone()
		table.insert(rows, class_row)
		table.insert(
			nested_ratings,
			1,
			bubble(class== and 'Unassessed' or (class..'‑class'), colour, conflict)
		)
	end
	add_category(category)
end
if args.HOOK_ASSESS then
	table.insert(rows, args.HOOK_ASSESS)
end
if raw_args.b1 or raw_args.b2 or raw_args.b3 or raw_args.b4 or raw_args.b5 or raw_args.b6 then
	local b_checklist = require(cfg.auxiliary_module .. (sandbox or )).b_checklist(args, raw_args, class, demo, assessment_link)
	table.insert(rows, b_checklist)
end
---------------------------
-- Importance assessment --
---------------------------
local importance = importance_mask(raw_args.importance or raw_args.priority, class, args.IMPORTANCE_SCALE, banner_name)
local importance_name = args.IMPN or (raw_args.priority and 'priority' or cfg.importance.default_name)
if importance then -- banner gives importance ratings
	local category = importance .. '-' .. importance_name .. ' ' .. assessment_cat
	if importance~='NA' then -- display importance rating
		local rating = importance=='Unknown' and cfg.importance.not_yet or cfg.importance.rated:format(importance, importance_name)
		local scale_name = cfg.importance.scale:format(importance_name)
		local scale = assessment_link
			and cfg.importance.project_scale:format(assessment_link..'#'..lang:ucfirst(scale_name), scale_name)
			or cfg.importance.default_scale
		local importance_rating = cfg.importance.rating:format(pagetype, rating, scale)
		local colour =  cfg.importance.colourimportance or cfg.importance.colour.default
		local importance_row =  mw.html.create('tr')
			:tag('td')
				:addClass('assess'):addClass('import'):addClass('import-' .. importance)
				:css('background', colour):wikitext(wikilink(':Category:' .. category, importance=='Unknown' and '???' or importance))
			:done()
			:tag('td'):addClass('mbox-text'):attr('colspan', '2'):wikitext(importance_rating)
		:allDone()
		table.insert(rows, importance_row)
		if importance~='Unknown' then -- importance is not NA or Unknown
			table.insert(
				nested_ratings,
				bubble(importance..'‑'..importance_name, colour)
			)
		end
	end
	add_category(category)
end
page_assessment(project, class, importance)
if args.HOOK_IMPORTANCE then
	table.insert(rows, args.HOOK_IMPORTANCE)
end
if args.QII_FORMAT then
	add_category(require(cfg.auxiliary_module .. (sandbox or )).quality_importance_insection(args, class, importance, importance_name))
end
---------------------------
-- Collapsing sections ----
---------------------------
local collapse_section = function(collapse, new_rows, header)
	if collapse then
		local collapsed_section = mw.html.create('tr')
		local collapsed_rows = collapsed_section:tag('td')
			:attr('colspan','3'):addClass('wpb-collapsed-notes'):tag('table')
				:addClass('mw-collapsible mw-collapsed'):tag('tr')
					:tag('th')
						:attr('colspan','3'):addClass('wpb-collapsed-head'):wikitext(header)
					:done()
				:done()
				:tag('tr')
					:tag('td')
						:addClass('mbox-image wpb-gutter'):css('min-width',image_left_size):tag('span'):addClass('wpb-iefix'):wikitext('/ '):
					done() --TO FIX IE
				:done()
				:tag('td'):done()
				:tag('td'):done()
			:done()
			for _, row in ipairs(new_rows) do
				collapsed_rows:node(row)
			end
		collapsed_section:allDone()
		table.insert(rows, collapsed_section)
	else
		for _, row in ipairs(new_rows) do
			table.insert(rows, row)
		end
	end
end
---------------------------
-- Task forces ------------
---------------------------
local nested_tf, taskforce_output = {}, {}
local tf_default_size = args.TF_SIZE or cfg.task_force.default_size
for _, k in ipairs(task_forces) do
	local tf_prefix = 'TF_' .. k .. '_'
	local tf_assessment_cat = argstf_prefix..'ASSESSMENT_CAT' or (argstf_prefix..'NAME' or )..' articles'
	local tf_importance
	if raw_args'tf '..k..' importance' then
		tf_importance = importance_mask(raw_args'tf '..k..' importance', class, args.IMPORTANCE_SCALE, banner_name)
		if tf_importance=='Unknown' and yesno(args.INHERIT_IMPORTANCE) then
			tf_importance = importance
		end
	end
	if argstf_prefix .. 'TEXT'~='none' then
		local portal = argstf_prefix..'PORTAL' and frame:expandTemplate{title='Portal', args={argstf_prefix .. 'PORTAL', height='15', margin='0'}} or 
		local text = 
		local tf_text = argstf_prefix..'TEXT' or args.TF_TEXT
		if tf_text then
			text = portal .. tf_text
				:gsub('_NAME_', argstf_prefix .. 'NAME' or )
				:gsub('_LINK_', argstf_prefix .. 'LINK' or )
				:gsub('_IMPORTANCE_', tf_importance or )
				:gsub('_PAGETYPE_', pagetype)
		else
			local tf_importance_text = tf_importance
				and tf_importance~='NA'
				and tf_importance~='Unknown'
				and ' ' .. cfg.task_force.importance:format(
					wikilink(':Category:' .. tf_importance .. '-' .. importance_name .. ' ' .. tf_assessment_cat, tf_importance .. '-' .. importance_name)
				) or 
			text = portal .. cfg.task_force.text:format(
				pagetype,
				wikilink(argstf_prefix .. 'LINK', argstf_prefix .. 'NAME'),
				tf_importance_text
			)
		end
		local tf_size = argstf_prefix .. 'SIZE' or tf_default_size
		local tf_image = 
		if argstf_prefix .. 'IMAGE' then
			tf_image = image(argstf_prefix .. 'IMAGE', tf_size, cfg.task_force.icon_alt, 'center')
		end
		local taskforce = mw.html.create('tr')
			:tag('td'):wikitext(tf_image):done()
			:tag('td'):addClass('mbox-text'):attr('colspan','2'):wikitext(text):allDone()
		table.insert(taskforce_output, taskforce)
	end
	if argstf_prefix..'HOOK' then
		table.insert(taskforce_output, argstf_prefix..'HOOK')
	end
	if yesno(argstf_prefix..'QUALITY') and class then
		local tf_class = check_exists(class, tf_assessment_cat)
		add_category((tf_class== and 'Unassessed' or tf_class..'-Class') .. ' ' .. tf_assessment_cat)
	end
	if tf_importance then
		add_category(tf_importance .. '-' .. importance_name .. ' ' .. tf_assessment_cat)
	end
	if argstf_prefix..'QII_FORMAT' then
		add_category(require(cfg.auxiliary_module .. (sandbox or )).quality_importance_insection(args, class, tf_importance, importance_name, tf_prefix))
	end
	if argstf_prefix..'NAME' then
		page_assessment(project..'/'..argstf_prefix..'NAME', class, tf_importance)
	end
	if argstf_prefix..'MAIN_CAT' then
		add_category(argstf_prefix..'MAIN_CAT')
	end
	if argstf_prefix..'NESTED' then
		table.insert(nested_tf, wikilink(argstf_prefix..'LINK', argstf_prefix..'NESTED'))
	end
	for _, c in ipairs(taskforce_categoriesk or {}) do-- add additional taskforce categories
		add_category(argstf_prefix..'CAT_'..c)
	end
end
if args.HOOK_TF then
	table.insert(taskforce_output, args.HOOK_TF)
end
local threshold = tonumber(args.TF_COLLAPSE) or (args.TF_HEADER and cfg.task_force.lower_threshold) or cfg.task_force.upper_threshold
collapse_section(
	#taskforce_output > threshold,
	taskforce_output,
	args.TF_HEADER or cfg.task_force.header
)
---------------------------
-- Notes ------------------
---------------------------
local note_output = {}
local note_default_size = args.NOTE_SIZE or args.NOTE_1_SIZE or cfg.note.default_size
local render_note = function(note_args)--text, image_name, size, category, sort_prefix
	local sort = note_args.sort_prefix and note_args.sort_prefix .. current_title.text
	add_category(note_args.category, sort)
	add_category(note_args.category2, sort)
	if note_args.text then
		local note_image = image(note_args.image_name, note_args.size or note_default_size, cfg.note.icon_alt, 'center')
		local new_note = mw.html.create('tr')
			:tag('td'):css('background', note_args.background):wikitext(note_image):done()
			:tag('td'):addClass('mbox-text'):attr('colspan', '2'):wikitext(note_args.text):allDone()
		table.insert(note_output, new_note)
		if note_image then
			local icon = mw.html.create('span')
				:addClass('wpb-header-bubbles')
				:wikitext('alt=')
				:done()
			table.insert(nested_ratings, tostring(icon))
		end
	end
end
local auto = false
local auto_arg = args.auto and args.auto:lower()
if (auto_arg=='yes' or auto_arg=='stub') and class=='Stub' then
	auto = 'stub'
elseif (auto_arg=='inherit' or auto_arg=='length') and class and class~= then
	auto = auto_arg
end
if auto then
	local auto_cat = args.AUTO_ASSESS_CAT or cfg.auto.default_cat:format(project)
	local auto_text = cfg.auto.assessed:format(
		pagetype,
		cfg.autoauto, -- method of automatic assessment
		parameter_format('auto')
	)
	local sort_prefix
	if auto=='stub' then
		sort_prefix = 'S'
	elseif auto=='length' then
		sort_prefix = 'L'
	elseif auto=='inherit' then
		local sort_codes = cfg.auto.sort_codes
		sort_prefix = sort_codesclass or cfg.auto.default_sort_code
	end
	render_note{
		text = auto_text,
		image_name =  cfg.auto.icon,
		category = auto_cat,
		sort_prefix = sort_prefix
	}
end
if yesno(args.attention, true) then
	local attention_cat = args.ATTENTION_CAT or cfg.attention.default_cat:format(project)
	render_note{
		text = cfg.attention.text:format(pagetype),
		image_name = cfg.attention.icon,
		category = attention_cat
	}
end
if yesno(args.infobox, true) then
	local infobox_cat = args.INFOBOX_CAT or cfg.infobox.default_cat:format(project)
	render_note{
		text = cfg.infobox.text:format(pagetype),
		image_name = cfg.infobox.icon,
		category = infobox_cat
	}
end
for _, k in ipairs(notes) do
	local note_prefix = 'NOTE_' .. k .. '_'
	render_note{
		text = parse_text(argsnote_prefix..'TEXT'),
		image_name = argsnote_prefix..'IMAGE',
		size = argsnote_prefix..'SIZE',
		category = argsnote_prefix..'CAT'
	}
end
if yesno(args'image-needed', true) then
	local image_needed_args = require(cfg.auxiliary_module .. (sandbox or )).image_needed(args, pagetype)
	render_note(image_needed_args)
end
if yesno(args'collaboration-candidate', true) or yesno(args'collaboration-current', true) or yesno(args'collaboration-past', true) then
	local collaboration_args = require(cfg.auxiliary_module .. (sandbox or )).collaboration(args, pagetype, current_title)
	render_note(collaboration_args.candidate)
	render_note(collaboration_args.current)
	render_note(collaboration_args.past)
end
if yesno(args'a class', true) then
	local a_class_args = require(cfg.auxiliary_module .. (sandbox or )).a_class(args, lang)
	render_note(a_class_args)
end
if yesno(args'peer review', true) or yesno(args'old peer review', true) then
	local peer_review_args = require(cfg.auxiliary_module .. (sandbox or )).peer_review(args, current_title)
	render_note(peer_review_args.current)
	render_note(peer_review_args.past)
end
local note_count = #note_output
if args.HOOK_NOTE then
	table.insert(note_output, args.HOOK_NOTE)
	local hook_collapsed = 0
	if args.HOOK_COLLAPSED then
		local success, result = pcall(mw.ext.ParserFunctions.expr, args.HOOK_COLLAPSED)
		hook_collapsed = success and tonumber(result) or 0
		if args.HOOK_COLLAPSED=='auto' then
			hook_collapsed = 1
		end
	end
	note_count = note_count + hook_collapsed
end
collapse_section(
	note_count > (tonumber(args.COLLAPSED) or cfg.note.threshold),
	note_output,
	args.COLLAPSED_HEAD or cfg.note.header
)
---------------------------
-- Bottom text ------------
---------------------------
if args.HOOK_BOTTOM then
	table.insert(rows, args.HOOK_BOTTOM)
end
if args.TODO_LINK or args.TODO_TEXT then
	local todolist = require(cfg.auxiliary_module .. (sandbox or )).todo_list(args, frame)
	table.insert(rows, todolist)
end
if args.BOTTOM_TEXT then
	local bottom_text = mw.html.create('tr')
		bottom_text
			:tag('td')
			:attr('colspan','3')
			:wikitext(parse_text(args.BOTTOM_TEXT))
	:allDone()
	table.insert(rows, bottom_text)
end
if args.MAIN_CAT then
	add_category(args.MAIN_CAT)
end
---------------------------
-- Nested display ---------
---------------------------
if args.HOOK_NESTED then
	local hook_nested = args.HOOK_NESTED:gsub('^ / ', ) -- remove initial slash, will be added later
	table.insert(nested_tf, hook_nested)
end
local nested_tf_str = 
if #nested_tf>0 then
	nested_tf_str = tostring(mw.html.create('span')
		:css('font-weight', 'normal')
		:wikitext(': ' .. table.concat(nested_tf, ' / '))
	)
end
local nested_ratings_str = #nested_ratings>0 and table.concat(nested_ratings, ' ') or 
if args.HOOK_NESTED_ASSESS then
	nested_ratings_str = nested_ratings_str .. tostring(mw.html.create('span')
		:addClass('wpb-header-bubbles')
		:wikitext(args.HOOK_NESTED_ASSESS)
	)
end
local header_row = mw.html.create('tr')
	:addClass('wpb-header')
	:tag('td')
		:addClass('wpb-header-icon')
		:attr('width', '50px')
		:css('text-align', 'center')
		:wikitext(image(args.IMAGE_LEFT, cfg.image.header_size, cfg.image.alt))
	:done()
	:tag('td')
		:addClass('wpb-header-combined')
		:wikitext(wikilink(project_link.prefixedText, project) .. nested_tf_str .. '  ' .. nested_ratings_str)
	:allDone()
---------------------------
-- Prepare categories -----
---------------------------
local categories_formatted = 
if args.demo_page then -- for testing purposes
	local category_list = mw.html.create('div')
		:css('background-color', '#F5F5F5'):css('border-width', '1px')
		:css('width', '500px')
		:wikitext('Categories:')
		:tag('ul')
	for _, cat in ipairs(categories) do
		local cat_link = wikilink(':Category:' .. cat.category, cat.category)
		category_list:tag('li'):wikitext(cat_link):done()
	end
	categories_formatted = tostring(category_list:allDone())
elseif not demo then
	local categories_linked = {}
	for _, cat in ipairs(categories) do
		local cat_link = wikilink('Category:' .. cat.category, cat.key)
		table.insert(categories_linked, cat_link)
	end
	categories_formatted = table.concat(categories_linked)
end
---------------------------
-- Make banner ------------
---------------------------
local status_class = (cfg.statusargs.PROJECT_STATUS or cfg.status.default_active) .. cfg.status.suffix
local banner = mw.html.create('table')
local banner_rows = banner
	:addClass('tmbox tmbox-notice mw-collapsible innercollapse wpb ' .. status_class)
	:css('table-layout', 'fixed')
	:node(header_row)
	:tag('tr')
		:tag('td')
			:addClass('mbox-text wpb-main')
			:attr('colspan','2')
			:tag('table')
				for _, row in ipairs(rows) do
					banner_rows:node(row)
				end
if args.listas then
	frame:preprocess()
end
local tstyle = frame:extensionTag ('templatestyles', , {src='Module:Message box/tmbox.css'}) ..
	frame:extensionTag ('templatestyles', , {src = 'มอดูล:ป้ายโครงการวิกิ' .. (sandbox or ) .. '/styles.css'})
return warning .. tstyle .. tostring(banner) .. categories_formatted, note_count, #taskforce_output, assessment_link
end
local parameter_check = function(frame, banner_name, project_name)
---------------------------
-- Unknown parameters -----
---------------------------
local parent_args = args_module.getArgs(frame, {parentOnly = true})
local parameters = {}
for parameter in banner_name:getContent():gmatch('