Module:Comment

From Zelda Wiki, the Zelda encyclopedia
Jump to navigation Jump to search

This module is invoked by templates in Category:Comment Templates.

The corresponding stylesheet for this module is Module:Comment/Styles.css.

This is the main module for the following templates: In addition, this module exports the following functions.

MultiComment

{{#invoke:Comment|MultiComment|<game>|<commenter>|<commenterFileName>|<borderColor>|headingPlural=|headingSingular=}}

Invoked by comment templates which support multiple quotes per subject. Examples include Template:Fi, Template:Midna, and Template:Tingle among others.

Parameters

ParameterStatusDescriptionDefault value
1gamerequiredA game code referring to the latest remake of the game in question.
2commenterrequiredThe name of the commenter in question. Should refer to an article name.
3commenterFileNamerequiredThe image to display for the commenter. Icon are preferred, e.g. File:SSHD Fi Icon.png.
4borderColoroptionalAn HTML color code, to use as a border color around the commenter's quote bubbles.
headingPluraloptionalSets the heading text for the comment when there are two or more quotes.commenter's Comments
headingSingularoptionalSets the heading text for the comment.commenter's Comment

SingleComment

{{#invoke:Comment|SingleComment|<game>|<commenter>|<commenterFileName>|<borderColor>|headingSingular=}}

Invoked by comment templates which support only one quote per subject. Examples include Template:Fishman, Template:Happy Mask Salesman, and Template:Madame Couture among others.

Parameters

ParameterStatusDescriptionDefault value
1gamerequiredA game code referring to the latest remake of the game in question.
2commenterrequiredThe name of the commenter in question. Should refer to an article name.
3commenterFileNamerequiredThe image to display for the commenter. Icon are preferred, e.g. File:SSHD Fi Icon.png.
4borderColoroptionalAn HTML color code, to use as a border color around the commenter's quote bubbles.
headingSingularoptionalSets the heading text for the comment when there is only one quote.commenter's Comment

GenerateDocumentation

{{#invoke:Comment|GenerateDocumentation|<function>|<game>|<commenter>
|bosses=
|characters=
|dungeons=
|enemies=
|locations=
|custom-topics=

|guidelines-placement=
|guidelines-formatting=
}}

Auto-generates documentation for comment templates. See for example Template:Midna/Documentation, Template:Monita/Documentation, Template:Fishman/Documentation.

Parameters

ParameterStatusDescriptionAccepted values
1functionrequiredThe name of the function invoked in the template. Determines whether to generate documentation for multi-comment or single-comment usage.
  • SingleComment
  • MultiComment
2gamerequiredThe game code of the game in question. Should be the same value as in the main template invocation, e.g. SSHD for Template:Fi.
3commenterrequiredThe commenter in question. This should be the same value as in the main template invocation, e.g. Fi for Template:Fi.
bossesoptionalSet this to true if the commenter comments on most bosses.
charactersoptionalSet this to true if the commenter comments on most characters.
dungeonsoptionalSet this to true if the commenter comments on most dungeons.
enemiesoptionalSet this to true if the commenter comments on most enemies.
locationsoptionalSet this to true if the commenter comments on most locations.
custom-topicsoptionalA comma-separated list of topics that the commenter comments on, in addition to or instead of the above. See Template:Tingle/Documentation and Template:Happy Mask Salesman/Documentation for example usages.
guidelines-placementoptionalCustom guidelines for how the template should be placed relative to other article content, if different from the usual standard for comment templates. See Template:Tingle/Documentation for example usage.
guidelines-formattingoptionalCustom guidelines for how quotes should be formatted, if different from the usual standard for comment templates. See Template:Monita/Documentation for example usage.

local p = {}

local Cite = require("Module:Cite")
local Franchise = require("Module:Franchise")
local utilsArg = require("Module:UtilsArg")
local utilsError = require('Module:UtilsError')
local utilsMarkup = require("Module:UtilsMarkup")
local utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")

local CAT_INVALID_ARGS = "[[Category:"..require("Module:Constants/category/invalidArgs").."]]"
local CLASS_PIXEL_ART = require("Module:Constants/class/pixelArt")
local COMMENTER_IMAGE_SIZE = "80x80px"
local MANUAL_LOGO_SIZE = "150px"
local STYLES = "Module:Comment/Styles.css"

-- [[Template:Manual]]
function p.Manual(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates.Manual)
	local errorCategories = err and utilsMarkup.categories(err.categories) or ""
	local result = p.printManualComment(args.game, args.subject, args.quote)
	return result..errorCategories
end

-- [[Template:Fi]], among others
function p.MultiComment(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates.MultiComment)
	local errorCategories = err and utilsMarkup.categories(err.categories) or ""
	local result = p.printComments(frame.args[1], frame.args[2], frame.args[3], frame.args[4], args.quotes,  frame.args["headingSingular"], frame.args["headingPlural"])
	return result..errorCategories
end

-- [[Template:Fishman]], among others
function p.SingleComment(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates.SingleComment)
	local errorCategories = err and utilsMarkup.categories(err.categories) or ""
	local quote = {
		quote = args.quote
	}
	local quotes = {quote}
	local result = p.printComments(frame.args[1], frame.args[2], frame.args[3], frame.args[4], quotes, frame.args["headingSingular"])
	return result..errorCategories
end	

-- Auto-generates documentation for comment templates
-- See [[Template:Fishman/Documentation]] for example
function p.GenerateDocumentation(frame)
	return p.documentation(frame)
end

function p.printManualComment(game, subject, quote)
	if not game or not subject or not quote then
		return ""
	end
	
	local gameArticle = Franchise.article(game)
	local gameDisplay = Franchise.display(game)
	local gameLogo = Franchise.logo(game)
	if not gameDisplay then -- game not defined at [[Data:Franchise]]
		return ""
	end
	gameLogo = utilsMarkup.file(gameLogo, {
		size = MANUAL_LOGO_SIZE,
		link = gameArticle,
		caption = gameDisplay .. " logo",
	})
	
	local manualEntry = mw.html.create("div")
		:addClass("comments__entry")
		:tag("div")
			:addClass("comments__entry-img")
			:wikitext(gameLogo)
			:done()
		:tag("div")
			:addClass("comments__entry-text")
			:tag("div")
				:addClass("comments__subject")
				:wikitext(subject)
				:done()
			:tag("div")
				:addClass("comments__description")
				:wikitext(quote)
				:done()
			:done()
			
	local quoteBubble = p.quoteBubble(manualEntry)
	local heading = string.format("%s Manual Description", gameDisplay)
	local modifiers = {"manual", "manual-"..utilsString.kebabCase(game)}
	
	local html = p.commentContainer(modifiers)
		:node(quoteBubble)
		:done()
		
	local collapsible = mw.getCurrentFrame():expandTemplate({
		title = "Collapsible",
		args = {
			header = heading,
			content = tostring(html)
		}
	})
	local styles = mw.getCurrentFrame():extensionTag({
		name = "templatestyles",
		args = { src = STYLES }
	})
    return styles..collapsible
end
	
function p.printComments(game, commenter, commenterFileName, quoteBorderColor, quotes, headingSingular, headingPlural)
	local categories = ""
	if #quotes == 0 then
		utilsError.warn("Please provide at least one quote.")
		categories = categories..CAT_INVALID_ARGS
		return categories
	end
	
	local img = utilsMarkup.file(commenterFileName, {
		size = COMMENTER_IMAGE_SIZE,
		link = commenter,
		caption = commenter.." says:",
		class = utilsString.endsWith(commenterFileName, "Sprite.png") and CLASS_PIXEL_ART or nil,
	})
	local commentsHeading
	if #quotes == 1 then
		commentsHeading = headingSingular or string.format("%s's Comment", commenter)
	else
		commentsHeading = headingPlural or string.format("%s's Comments", commenter)
	end
	
	local modifier = utilsString.kebabCase(commenter)
	local content = p.commentContainer({modifier})
		
	local quote = quotes[1]
	local additionalQuotes = utilsTable.tail(quotes)
	local firstComment = content:tag("div")
		:addClass("comments__first-comment")
	if quote.context then
		firstComment:tag("span")
			:addClass("comments__quote-context")
			:wikitext(quote.context)
	end
	firstComment:tag("div")
		:addClass("comments__commenter")
		:wikitext(img)
	local coloredQuote = Cite.color(quote.quote, commenter, game)
	local quoteBubble = p.quoteBubble(coloredQuote, quoteBorderColor)
	firstComment:node(quoteBubble)
	
	if #additionalQuotes > 0 then
		local collapsedContent = content:tag("div")
			:addClass("mw-collapsible mw-collapsed comments__additional-comments")
			:attr("id", "mw-customcollapsible-additionalComments"..commenter)
			:tag("span")
				:addClass("comments__toggle-more mw-customtoggle-additionalComments"..commenter)
				:wikitext("show more...")
				:done()
			:tag("div")
				:addClass("mw-collapsible-content comments__additional-comments-content")
		for i, quote in ipairs(additionalQuotes) do
			collapsedContent:tag("div")
				:addClass("comments__spacer")
				:attr("aria-hidden", "true")
			if quote.context then
				collapsedContent:tag("span")
					:addClass("comments__quote-context")
					:wikitext(quote.context)
			elseif commenter ~= "Fi" then
				-- we make an exception for Fi because for bosses she has several comments separated by "tell me more" prompts
				-- It would be redundant to show "tell me more" for multiple quotes
				utilsError.warn("The <code>context</code> parameter should be used when there are more than one comment.")
				categories = categories .. string.format("[[Category:%s articles needing attention]]", Franchise.shortName(Franchise.baseGame(game)))
			end
			local coloredQuote = Cite.color(quote.quote, commenter, game)
			local quoteBubble = p.quoteBubble(coloredQuote, quoteBorderColor)
			collapsedContent:node(quoteBubble)
		end
	end
	local result = tostring(content:allDone())
	local collapsible = mw.getCurrentFrame():expandTemplate({
		title = "Collapsible",
		args = {
			header = commentsHeading,
			content = result,
		}
	})
	local styles = mw.getCurrentFrame():extensionTag({
		name = "templatestyles",
		args = { src = STYLES }
	})
	return styles..collapsible..categories
end

function p.commentContainer(modifiers)
	-- Custom class names use BEM syntax https://getbem.com/
	local blockClasses = "comments"
	for i, modifier in ipairs(modifiers) do
		blockClasses = blockClasses.." comments--"..modifier -- modifier classes allow custom styling for different comment templates
	end
	return mw.html.create("div")
		:addClass(blockClasses)
end

function p.quoteBubble(quote, borderColor)
	local bubble = mw.html.create("blockquote")
		:addClass("comments__quote-bubble")
		-- We use an inline style here so that folks can make new comment templates 
		-- without having to edit two additional CSS files, which few know how to or have the rights to do
		:css("border-color", borderColor)
		
	if type(quote) == "string" then
		bubble:wikitext(quote)
	else -- assume the quote is an mw.html builder object
		bubble:node(quote)
	end
	
	return bubble
end

function p.documentation(frame)
	-- Only the documentation needs these so we require them here as an optimization
	local Documentation = require("Module:Documentation")
	local utilsString = require("Module:UtilsString")
	
	local args = utilsTable.mapValues(frame.args, utilsString.trim)
	args = utilsTable.mapValues(args, utilsString.nilIfEmpty)
	local templateName = args[1]
	local game = args[2]
	local commenter = args[3]
	local examples = args.examples
	
	local baseGame = Franchise.baseGame(game)
	local gameName =  Franchise.shortName(baseGame)
	local gameDisplay = Franchise.display(baseGame)
	local topics = {
		args["bosses"] == "true" and args["enemies"] ~= "true" and string.format("[[Bosses in %s|bosses]]", gameName),
		args["characters"] == "true" and string.format("[[Characters in %s|characters]]", gameName),
		args["dungeons"] == "true" and args["dungeons"] ~= "true" and string.format("[[Dungeons in %s|dungeons]]", gameName),
		args["enemies"] == "true" and string.format("[[Enemies in %s|enemies]]", gameName),
		args["locations"] == "true" and string.format("[[Locations in %s|locations]]", gameName),
	}
	topics = utilsTable.compact(topics)
	local customTopics = args["custom-topics"]
	if customTopics then
		customTopics = utilsString.split(customTopics)
		topics = utilsTable.concat(topics, customTopics)
	end
	topics = mw.text.listToText(topics)
	local templatePurpose = string.format("This template displays quote bubbles containing [[%s]]'s comments on %s in %s.", commenter, topics, gameDisplay)
	local templateSpec = utilsTable.merge(p.Templates[templateName], {
		purpose = templatePurpose 
	})
	local templateDoc = Documentation.printTemplateDoc("Module:Comments", templateName, templateSpec)
	
	if examples then
		templateDoc = templateDoc .. utilsMarkup.heading(3, "Examples") .. "\n"
		templateDoc = templateDoc .. frame:preprocess(examples)
	end
	
	-- Guidelines
	templateDoc = templateDoc .. utilsMarkup.heading(2, "Guidelines") .. "\n"
	
	local customPlacementGuidelines = args["guidelines-placement"]
	local customFormattingGuidelines = args["guidelines-formatting"]
	
	if not utilsString.isEmpty(customPlacementGuidelines) then
		templateDoc = templateDoc .. frame:preprocess(customPlacementGuidelines)
	else
		templateDoc = templateDoc .. string.format("This template should be placed on the line beneath the %s heading, or under the first heading if the article is about %s specifically. There should be one line of empty space between the template transclusion and the article content.", gameDisplay, gameDisplay)
	end
	templateDoc = templateDoc .. "\n\n"
	if not utilsString.isEmpty(customFormattingGuidelines) then
		templateDoc = templateDoc .. frame:preprocess(customFormattingGuidelines)
	else
		templateDoc = templateDoc .. frame:preprocess("Use <code><nowiki><p></nowiki></code> tags to separate blocks of dialogue. A block of dialogue consists of all the text that appears before the player must prompt the dialogue to continue using their controller.")
	end
	if Franchise.isRemake(game) then
		templateDoc = templateDoc .. string.format("\n\nAll quotes should reference the updated remake of the game, %s. If the comment from a previous iteration of the game is notably different, it can be included as a separate quote.", Franchise.link(game))
	end
		
	return templateDoc
end

local quoteDesc = string.format("The complete description given by %s on the subject, quoted word-for-word. Use [[Template:Color]], [[Template:Icon]], [[Template:Player Name]], and [[Template:Typo]] as needed.", mw.title.getCurrentTitle().baseText)

p.Templates = {
	Manual = {
		format = "inline",
		purpose = "This template is used to provide a text box holding the comments on various {{Plural|Series|Enemy|link}}, {{Plural|Series|Item|link}}, Locations, and Characters in {{TLoZ|Series}} that are given by game manuals.",
		params = {
			[1] = {
				name = "game",
				required = true,
				type = "string",
				enum = Franchise.enum({includeGroups = true}), -- includeGroups is for ALttP&FS
				desc = "The abbreviation for the game the manual is associated with.",
				trim = true,
				nilIfEmpty = true,
			},
			[2] = {
				name = "subject",
				required = true,
				type = "string",
				desc = "The name of the subject being described, as it appears in the manual.",
				trim = true,
				nilIfEmpty = true,
			},
			[3] = {
				name = "quote",
				required = true,
				type = "content",
				desc = "The complete description given by the manual on the subject, quoted word-for-word. Use [[Template:Color]], [[Template:Icon]], [[Template:Player Name]], and [[Template:Typo]] as needed.",
				trim = true,
				nilIfEmpty = true,
			},
		},
		examples = {
			{"ALttP", "Bomb", "A bomb blast will damage enemies and knock holes in some walls, but it will also damage your character if he is too close to the blast. The bomb's fuse will burn for about two seconds, and up to two bombs can be set at a time. You can also pick up a bomb you have placed and throw it before it explodes (be careful!)."},
			{"ALttP&FS", "Bomb", "You can set up to two bombs at a time. You can also pick them up and throw them."},
			{"alttp", "Bomb", "Inputting the game abbreviation in lowercase still works, but the template will complain about it with a [[Template:Warn|warning]]."},
			{"notAGame", "foo", "bar"},
			{
				desc = "All parameters are required.",
				args = {},
			},
		},
	},
	SingleComment = {
		format = "inline",
		params = {
			[1] = {
				name = "quote",
				required = true,
				type = "content",
				desc = quoteDesc,
				trim = true,
				nilIfEmpty = true,
			},
		},
	},
	MultiComment = {
		format = "block",
		repeatedGroup = {
			name = "quotes",
			params = {"context", "quote"},
			counts = {1, 2, 3, 4, 5, 6, 7, 8},
		},
		params = {
			quote = {
				required = true,
				type = "content",
				desc = quoteDesc,
				trim = true,
				nilIfEmpty = true,
			},
			context = {
				type = "string",
				desc = "A description of where or when the quote appears. Only needed when there are multiple quotes in different contexts. Generally not needed for the first quote.",
				type = "string",
				trim = true,
				nilIfEmpty = true,
			},
		},
	},
}

function p.Documentation()
	return {
		MultiComment = {
			desc = "Invoked by [[:Category:Comment Templates|comment templates]] which support multiple quotes per subject. Examples include [[Template:Fi]], [[Template:Midna]], and [[Template:Tingle]] among others.",
			frameParams = {
				[1] = {
					name = "game",
					required = true,
					desc = "A [[Data:Franchise|game code]] referring to the latest remake of the game in question.",
				},
				[2] = {
					name = "commenter",
					required = true,
					desc = "The name of the commenter in question. Should refer to an article name.",
				},
				[3] = {
					name = "commenterFileName",
					required = true,
					desc = "The image to display for the commenter. Icon are preferred, e.g. <code>File:SSHD Fi Icon.png</code>."
				},
				[4] = {
					name = "borderColor",
					desc = "An HTML color code, to use as a border color around the commenter's quote bubbles.",
				},
				["headingSingular"] = {
					desc = "Sets the heading text for the comment.",
					default = "<code>commenter</code>'s Comment",
				},
				["headingPlural"] = {
					desc = "Sets the heading text for the comment when there are two or more quotes.",
					default = "<code>commenter</code>'s Comments",
				},
			},
		},
		SingleComment = {
			desc = "Invoked by [[:Category:Comment Templates|comment templates]] which support only one quote per subject. Examples include [[Template:Fishman]], [[Template:Happy Mask Salesman]], and [[Template:Madame Couture]] among others.",
			frameParams = {
				[1] = {
					name = "game",
					required = true,
					desc = "A [[Data:Franchise|game code]] referring to the latest remake of the game in question.",
				},
				[2] = {
					name = "commenter",
					required = true,
					desc = "The name of the commenter in question. Should refer to an article name.",
				},
				[3] = {
					name = "commenterFileName",
					required = true,
					desc = "The image to display for the commenter. Icon are preferred, e.g. <code>File:SSHD Fi Icon.png</code>."
				},
				[4] = {
					name = "borderColor",
					desc = "An HTML color code, to use as a border color around the commenter's quote bubbles.",
				},
				["headingSingular"] = {
					desc = "Sets the heading text for the comment when there is only one quote.",
					default = "<code>commenter</code>'s Comment",
				},
			},
		},
		GenerateDocumentation = {
			desc = "Auto-generates documentation for comment templates. See for example [[Template:Midna/Documentation]], [[Template:Monita/Documentation]], [[Template:Fishman/Documentation]].",
			frameParamsOrder = {"bosses", "characters", "dungeons", "enemies", "locations", "custom-topics", "guidelines-placement", "guidelines-formatting"},
			frameParamsFormat = "multiLine",
			frameParams = {
				[1] = {
					name = "function",
					required = true,
					inline = true,
					enum = {"SingleComment", "MultiComment"},
					desc = "The name of the function invoked in the template. Determines whether to generate documentation for multi-comment or single-comment usage.",
				},
				[2] = {
					name = "game",
					required = true,
					inline = true,
					desc = "The [[Data:Franchise|game code]] of the game in question. Should be the same value as in the main template invocation, e.g. <code>SSHD</code> for Template:Fi.",
				},
				[3] = {
					name = "commenter",
					required = true,
					inline = true,
					desc = "The commenter in question. This should be the same value as in the main template invocation, e.g. <code>Fi</code> for Template:Fi.",
				},
				bosses = {
					desc = "Set this to <code>true</code> if the commenter comments on most bosses.",
				},
				characters = {
					desc = "Set this to <code>true</code> if the commenter comments on most characters.",	
				},
				dungeons = {
					desc = "Set this to <code>true</code> if the commenter comments on most dungeons.",
				},
				enemies = {
					desc = "Set this to <code>true</code> if the commenter comments on most enemies.",	
				},
				locations = {
					desc = "Set this to <code>true</code> if the commenter comments on most locations."	,
				},
				["custom-topics"] = {
					desc = "A comma-separated list of topics that the commenter comments on, in addition to or instead of the above. See [[Template:Tingle/Documentation]] and [[Template:Happy Mask Salesman/Documentation]] for example usages.",
				},
				["guidelines-placement"] = {
					desc = "Custom guidelines for how the template should be placed relative to other article content, if different from the usual standard for comment templates. See [[Template:Tingle/Documentation]] for example usage.",
					spaceBefore = true,
				},
				["guidelines-formatting"] = {
					desc = "Custom guidelines for how quotes should be formatted, if different from the usual standard for comment templates. See [[Template:Monita/Documentation]] for example usage.",
				},
			},
		}
	}
end

return p