Module:Color

local p = {} local h = {}

local utilsPackage = require("Module:UtilsPackage") local utilsString = require("Module:UtilsString") local _Arguments = utilsPackage.lazyLoad("Module:Arguments") local _utilsError = utilsPackage.lazyLoad("Module:UtilsError")

local Constants = mw.loadData("Module:Constants/Data") local data = mw.loadData("Module:Color/Data")

local CAT_INVALID_ARGS = "" local CAT_INVALID_COLOR = ""

function p.Main(frame) local args = frame:getParent.args local coloredText, errorCategories = p.color(args[1], args[2]) return coloredText .. (errorCategories or "") end

function p.List(frame) local args = frame:getParent.args return p.listColors(args[1]) end

-- @return the colored text as a string -- @return any error categories as a string, or nil function p.color(colorId, text) if text == nil or text == "" then h.warn(" parameter is required.") return text or "", CAT_INVALID_ARGS end if colorId == nil or colorId == "" then h.warn(" parameter is required.") return text, CAT_INVALID_ARGS end if string.find(colorId, "\n", 1, true) or string.find(colorId, "\r", 1, true) or utilsString.startsWith(colorId, " ") or utilsString.endsWith(colorId, " ") then h.warn(" argument contains invalid whitespace such as newlines or leading/trailing spaces.") return text, CAT_INVALID_ARGS end local colorData = data.colors[colorId] if colorData == nil then h.invalidColor(colorId) return text, CAT_INVALID_COLOR end local colorValue = colorData.color local html = mw.html.create("span") :addClass("colored-text") :wikitext(text) if utilsString.startsWith(colorValue, "linear-gradient") then -- It's OK to use inline styles here because the color is a "fixed" part of the quote and applies to all Zelda Wiki skins -- This enables colors to be defined in one place in module data, which is easier to maintain html:css({			["background-image"] = colorValue,			["color"] = "transparent",			["background-clip"] = "text",			["-webkit-background-clip"] = "text"		}) else html:css("color", colorValue) end local result = tostring(html) if colorData.deprecated then local action = "" if type(colorData.deprecated) == "string" then action = string.format("Use  instead.", colorData.deprecated) else action = "See Template:Color for a list of supported colors." end h.warn(string.format("Color  is being discontinued. %s", colorId, action)) return result, CAT_INVALID_COLOR end return result end

function h.invalidColor(colorId) h.warn(string.format(" is not a valid color name. See Template:Color for a list of supported colors.", colorId)) _Arguments.store({		module = "Module:Color",		args = {			[1] = colorId,			[2] = text,		},		isValid = false,	}) end

function h.warn(msg) _utilsError.warn(msg) end

-- lists colors for one game, or all games if game is nil function p.listColors(game) -- Perfomance optimization; only loading dependencies on the documentation pages where they're needed local Franchise = require("Module:Franchise") local utilsLayout = require("Module:UtilsLayout") local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable") local tableRows = { {			header = true, {				content = "Color", styles = { width = "150px" },			},			"Name", "Notes", }	}	local games = game and {game} or Franchise.enum({includeNonfiction = true}) local listAllGames = game == nil local colorKeys = utilsTable.keys(data.colors) -- Remove deprecated colors colorKeys = utilsTable.filter(colorKeys, function(colorKey)		return not data.colors[colorKey].deprecated	end) local seenColorKeys = {} for _, game in ipairs(games) do -- "game" here could also be a book like Encyclopedia -- The added " " is for doing a whole-word match. e,g. SS should match "SS Green" but not "SSHD Green" local gameColorKeys = utilsTable.filter(colorKeys, utilsString._startsWith(game .. " "))		table.sort(gameColorKeys) if listAllGames and #gameColorKeys > 0 then table.insert(tableRows, {				header = true,				{					colspan = 3,					content = string.format(' %s ', game, game, Franchise.display(game)), -- creates section links to each game				}			}) end p.addRows(tableRows, gameColorKeys) seenColorKeys = utilsTable.concat(seenColorKeys, gameColorKeys) end

local remainingColorKeys = utilsTable.difference(colorKeys, seenColorKeys) if listAllGames and #remainingColorKeys > 0 then table.insert(tableRows, {			header = true,			{				colspan = 3,				content = string.format("Other "),			}		}) p.addRows(tableRows, remainingColorKeys) end return utilsLayout.table({		caption = "Text Colors",		rows = tableRows,		hideEmptyColumns = true,	}) end function p.addRows(tableRows, gameColorKeys) for _, colorKey in ipairs(gameColorKeys) do		local colorData = data.colors[colorKey] if not colorData.deprecated then table.insert(tableRows, {				{					content = " ", -- using whitespace so the cell isn't treated as being empty by utilsLayout					styles = {						["background"] = colorData.color,					}				},				" ",				colorData.notes or ""			}) end end end

-- Category:Articles Using Invalid Color Names function p.ReportInvalidColors(frame) local utilsLayout = require("Module:UtilsLayout") local utilsMarkup = require("Module:UtilsMarkup") local utilsTable = require("Module:UtilsTable") local invalidColors = _Arguments.query({		module = "Module:Color",		parameter = 1,		isValid = false,	}) invalidColors = utilsTable.uniqueBy(invalidColors, "_pageName") invalidColors = utilsTable.groupBy(invalidColors, "argument") invalidColors = utilsTable.mapValues(invalidColors, utilsTable._map("_pageName")) local rows = {} for colorName, pages in pairs(invalidColors) do		colorName = " " local pageLinks = utilsTable.map(pages, utilsMarkup.link) local pageList = table.concat(pageLinks, " • ") table.insert(rows, {colorName, #pages, pageList}) end -- sort descending by # of pages then ascending by color name table.sort(rows, function(a, b)		if a[2] == b[2] then			return a[1] < b[1]		else			return a[2] > b[2]		end	end)

local wikitable = utilsLayout.table({		headers = {"Invalid Color Name", "Page Count", "Pages"},		rows = rows,		sortable = true,	}) return wikitable end

-- For auto-generated doc at Module:Colors/Data/Documentation function p.Data return p.listColors end

p.Templates = { ["Color"] = { purpose = "This template allows text to be colored such that quotes from games (e.g. Template:Cite) or other media have the same color highlights as the source text. These colors convey meaningful empasis and are essential to Zelda Wiki's terminology guidelines.", format = "inline", params = { [1] = {				name = "colorId", required = true, type = "string", desc = "The name of the color - see supported colors below" },			[2] = {				name = "text", required = true, type = "string", desc = "The text to color" }		},		examples = { {"TWWHD Vermilion", "merchant's oath"}, {"SSHD Blue", "Demise"}, {"invalid color", "foo"}, {				desc = "This template no longer supports arbitrary web colors. Use Template:Web Color instead. Name all in-game colors at Module:Color/Data.", args = {"#f2a1b5", "custom color"}, },			{				desc = "Leading/trailing whitespace is not allowed in the color name." ,				args = {"SSHD Blue ", "Demise"}, },			{"SSHD\nBlue", "Demise"}, {				desc = "Both parameters are required.", args = {"", "missing color"}, },			{"missing text"}, {""},			{},			{				desc = "Color names can be discontinued.", args = {"deprecated color", "bar"}, },			{"replaced color", "bar"}, }	},	["Color/List"] = { purpose = "This template lists the colors that Template:Color supports.", format = "inline", params = { [1] = {				name = "game", type = "string", enum = { reference = "Data:Franchise" },				desc = "If specified, the template will output the colors for the given game only. Otherwise, all colors will be listed.", canOmit = true, }		},		examples = { {"BotW"}, {"E"}, }	} }

p.Schemas = { -- For auto-generated doc at Module:Color/Documentation color = { colorName = { type = "string", required = true, desc = "The name of a color from Module:Color/Data", },		text = { type = "string", required = true, desc = "The text to color", },	},	-- For auto-generated doc at Module:Color/Data/Documentation Data = { type = "record", required = true, properties = { {				name = "colors", required = true, type = "map", keys = { type = "string", },				values = { type = "record", properties = { {							name = "color", required = true, type = "string", desc = "A hex color (with preceding #) or a linear-gradient.", },						{							name = "notes", type = "string", desc = "Anything that editors should know about the color – the context in which it appears, other colors it could be confused with, etc.", },						{							name = "deprecated", oneOf = { {									type = "string" },								{									type = "boolean" },							},							desc = "The name of the color that should be used instead of this color going forward. If multiple colors apply, just put .", }					}				}			}		}	} }

p.Documentation = { color = { params = {"colorName", "text"}, returns = { "Colored text", "Error categories, or  if no errors occurred", },		cases = { {				args = {"TMC Blue", "guy in green tights"}, expect = {' guy in green tights ', nil}, },			{				args = {"notAColor", "foo"}, expect = {"foo", ""}, },		},	}, }

return p