Module:Comment

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 = "" 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["heading-singular"], frame.args["heading-plural"]) 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["heading-singular"]) 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(heading, modifiers) :node(quoteBubble) :done local styles = mw.getCurrentFrame:extensionTag({		name = "templatestyles",		args = { src = STYLES }	}) return styles..tostring(html) 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(commentsHeading, {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  parameter should be used when there are more than one comment.") categories = categories .. string.format("", 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 styles = mw.getCurrentFrame:extensionTag({		name = "templatestyles",		args = { src = STYLES }	}) return styles..result..categories end

function p.commentContainer(heading, 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("mw-collapsible "..blockClasses) :attr("data-expandtext", "show ▼") :attr("data-collapsetext", "hide ▲") :tag("div") :addClass("comments__heading mw-collapsible-toggle") :tag("span") :addClass("comments__heading-text") :wikitext(heading) :done :tag("span") :addClass("comments__toggle-button") :tag("span") :addClass("comments__toggle-button-text mw-collapsible-text") :wikitext("hide ▲") :done :done :done :tag("div") :addClass("mw-collapsible-content comments__content") 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", gameName), args["characters"] == "true" and string.format("characters", gameName), args["dungeons"] == "true" and args["dungeons"] ~= "true" and string.format("dungeons", gameName), args["enemies"] == "true" and string.format("enemies", gameName), args["locations"] == "true" and string.format("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  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 NaN Enemies, NaN Items, Locations, and Characters in 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 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 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 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. ." },				[4] = {					name = "borderColor", desc = "An HTML color code, to use as a border color around the commenter's quote bubbles.", },				["heading-singular"] = { desc = "Sets the heading text for the comment.", default = " 's Comment", },			},		},		SingleComment = { desc = "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.", frameParams = { [1] = {					name = "game", required = true, desc = "A 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. ." },				[4] = {					name = "borderColor", desc = "An HTML color code, to use as a border color around the commenter's quote bubbles.", },				["heading-singular"] = { desc = "Sets the heading text for the comment when there is only one quote.", default = " 's Comment", },				["heading-plural"] = { desc = "Sets the heading text for the comment when there are two or more quotes.", default = " 's Comments", },			},		},		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 game code of the game in question. Should be the same value as in the main template invocation, e.g.  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.  for Template:Fi.", },				bosses = { desc = "Set this to  if the commenter comments on most bosses.", },				characters = { desc = "Set this to  if the commenter comments on most characters.", },				dungeons = { desc = "Set this to  if the commenter comments on most dungeons.", },				enemies = { desc = "Set this to  if the commenter comments on most enemies.", },				locations = { desc = "Set this to  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