Module:Color: Difference between revisions

From Zelda Wiki, the Zelda encyclopedia
Jump to navigation Jump to search
(Add color validation, color deprecation, Template:Color documentation.)
No edit summary
 
(37 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local utilsError = require("Module:UtilsError")
local p = {}
local h = {}
local data = mw.loadData("Module:Color/Data")
 
local utilsString = require("Module:UtilsString")
local utilsString = require("Module:UtilsString")


local data = mw.loadData("Module:Color/Data")
local CAT_INVALID_ARGS = "[[Category:"..require("Module:Constants/category/invalidArgs").."]]"
local CAT_INVALID_COLOR = "[[Category:Articles using invalid color names]]"


local p = {}
function h.warn(msg)
local utilsError = require("Module:UtilsError")
utilsError.warn(msg)
end


local CAT_DEPRECATED_COLORS = "Category:Articles Using Deprecated Colors"
function h.invalidColor(colorId)
local TransclusionArguments = require("Module:Transclusion Arguments")
h.warn(string.format("<code>%s</code> is not a valid color name. See [[Template:Color]] for a list of supported colors.", colorId))
-- We only store invalid colors for performance reasons
-- Storing all template usages seems to break pages that use the template a lot
TransclusionArguments.store({
module = "Module:Color",
args = {
[1] = colorId,
[2] = text,
},
isValid = false,
})
end


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


Line 18: Line 39:
end
end


-- There is intentationally duplicated code at [[Module:Cite]] - see [[wikipedia:Rule of three]]
-- @return the colored text as a string
-- @return any error categories as a string, or nil
function p.color(colorId, text)
function p.color(colorId, text)
if text == nil or text == "" then
h.warn("<code>text</code> parameter is required.")
return text or "", CAT_INVALID_ARGS
end
if colorId == nil or colorId == "" then
h.warn("<code>color</code> 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("<code>color</code> argument contains invalid whitespace such as newlines or leading/trailing spaces.")
return text, CAT_INVALID_ARGS
end
local colorData = data.colors[colorId]
local colorData = data.colors[colorId]
if colorData == nil then
if colorData == nil then
utilsError.warn(string.format("<code>%s</code> is not a valid color name. See [[Template:Color]] for the list of supported colors.", colorId))
h.invalidColor(colorId)
return text .. "[[Category:Articles Using Invalid Arguments in Template Calls]]"
return text, CAT_INVALID_COLOR
end
end
local colorValue = colorData.color
local colorValue = colorData.color
local html = mw.html.create("span")
local html = mw.html.create("span")
:addClass("colored-text")
:addClass("zw-color")
:addClass("colored-text-highlight") -- to distinguish from the colored-text added by [[Module:Color]]
:wikitext(text)
:wikitext(text)
if utilsString.startsWith(colorValue, "linear-gradient") then
if utilsString.startsWith(colorValue, "linear-gradient") then
Line 40: Line 74:
})
})
else
else
html:css({
html:css("color", colorValue)
["color"] = colorValue
})
end
end
local result = tostring(html)
local result = tostring(html)
if colorData.deprecated then
if colorData.deprecated then
utilsError.warn(string.format("Color <code>%s</code> is being discontinued. Please update it to a current color listed at [[Template:Color]].", colorId))
local action = ""
result = result.."[["..CAT_DEPRECATED_COLORS.."]]"
if type(colorData.deprecated) == "string" then
action = string.format("Use <code>%s</code> instead.", colorData.deprecated)
else
action = "See [[Template:Color]] for a list of supported colors."
end
h.warn(string.format("Color <code>%s</code> is being discontinued. %s", colorId, action))
return result, CAT_INVALID_COLOR
end
end
return result
return result
Line 73: Line 111:
}
}
}
}
local games = game and {game} or Franchise.enum()
local games = game and {game} or Franchise.enum({includeNonfiction = true})
local listAllGames = game == nil
local listAllGames = game == nil
local colorKeys = utilsTable.keys(data.colors)
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
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"
-- The added " " is for doing a whole-word match. e,g. SS should match "SS Green" but not "SSHD Green"
Line 85: Line 130:
{
{
colspan = 3,
colspan = 3,
content = Franchise.display(game),
content = string.format('<span id="%s">[[#%s|%s]]</span>', game, game, Franchise.display(game)), -- creates section links to each game
}
}
})
})
end
end
for _, colorKey in ipairs(gameColorKeys) do
p.addRows(tableRows, gameColorKeys)
local colorData = data.colors[colorKey]
seenColorKeys = utilsTable.concat(seenColorKeys, gameColorKeys)
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,
}
},
"<b><code>"..colorKey.."</code></b>",
colorData.notes or ""
})
end
end
end
end
local remainingColorKeys = utilsTable.difference(colorKeys, seenColorKeys)
if listAllGames and #remainingColorKeys > 0 then
table.insert(tableRows, {
header = true,
{
colspan = 3,
content = string.format("[[#Other|Other]]</span>"),
}
})
p.addRows(tableRows, remainingColorKeys)
end
return utilsLayout.table({
return utilsLayout.table({
caption = "Text Colors",
caption = "Text Colors",
Line 110: Line 154:
hideEmptyColumns = true,
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,
}
},
"<b><code>"..colorKey.."</code></b>",
colorData.notes or ""
})
end
end
end
end


Line 123: Line 184:
params = {
params = {
[1] = {
[1] = {
name = "color",
name = "colorId",
required = true,
required = true,
type = "string",
type = "string",
Line 137: Line 198:
examples = {
examples = {
{"TWWHD Vermilion", "merchant's oath"},
{"TWWHD Vermilion", "merchant's oath"},
{"SSHD Fi Blue", "Demise"},
{"SSHD Blue", "Demise"},
{"invalid color", "foo"},
{"invalid color", "foo"},
{"deprecated color", "bar"},
{
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"},
}
}
},
},
Line 163: Line 244:
}
}


-- For auto-generated doc at Module:Colors/Data/Documentation
function p.Schemas()
p.Schemas = {
return {
Data = {
-- For auto-generated doc at Module:Color/Documentation
type = "record",
color = {
required = true,
colorName = {
properties = {
type = "string",
{
required = true,
name = "colors",
desc = "The name of a color from [[Module:Color/Data]]",
},
text = {
type = "string",
required = true,
required = true,
type = "map",
desc = "The text to color",
keys = {
},
type = "string",
},
},
-- For auto-generated doc at Module:Color/Data/Documentation
values = {
Data = {
type = "record",
type = "record",
properties = {
required = true,
{
properties = {
name = "color",
{
required = true,
name = "colors",
type = "string",
required = true,
desc = "A [https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color hex color] (with preceding #) or a [https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient linear-gradient].",
type = "map",
},
keys = {
{
type = "string",
name = "notes",
},
type = "string",
values = {
desc = "Anything that editors should know about the color – the context in which it appears, other colors it could be confused with, etc.",
type = "record",
},
properties = {
{
{
name = "deprecated",
name = "color",
type = "boolean",
required = true,
desc = "Set this to true for colors that are to be removed or replaced. Pages using this color are added to [[:"..CAT_DEPRECATED_COLORS.."]].<br/>Please indicate in <code>notes</code> what color should be used instead."
type = "string",
desc = "A [https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color hex color] (with preceding #) or a [https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient linear-gradient].",
},
{
name = "isDefault",
type = "boolean",
desc = "Set this to <code>true</code> when the color is the game's default dialogue color. Used by [[Module:Cite]] to set the default text color for citations."
},
{
name = "defaultFor",
type = "array",
items = { type = "string" },
desc = "A list of wiki page names referring to the characters and/or interfaces whose text uses the color by default. Used by [[Module:Cite]] to set the default text color for citations."
},
{
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 <code>true</code>.",
},
}
}
}
}
Line 200: Line 314:
}
}
}
}
}
end
 
function p.Documentation()
return {
color = {
params = {"colorName", "text"},
returns = {
"Colored text",
"Error categories, or <code>nil</code> if no errors occurred",
},
cases = {
{
args = {"TMC Blue", "guy in green tights"},
expect = {'<span class="zw-color" style="color:#37acbe">guy in green tights</span>', nil},
},
{
args = {"notAColor", "foo"},
expect = {"foo", "[[Category:Articles using invalid color names]]"},
},
},
},
}
end


return p
return p

Latest revision as of 01:50, 21 July 2023

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

color

color(colorName, text)

Parameters

Returns

  • Colored text
  • Error categories, or nil if no errors occurred

Examples

#InputOutputResultStatus
1
color("TMC Blue", "guy in green tights")
'<span class="zw-color" style="color:#37acbe">guy in green tights</span>'
guy in green tights
Green check.svg
nil
Green check.svg
2
color("notAColor", "foo")
"foo"
foo
Green check.svg
"[[Category:Articles using invalid color names]]"
Green check.svg

local p = {}
local h = {}
local data = mw.loadData("Module:Color/Data")

local utilsString = require("Module:UtilsString")

local CAT_INVALID_ARGS = "[[Category:"..require("Module:Constants/category/invalidArgs").."]]"
local CAT_INVALID_COLOR = "[[Category:Articles using invalid color names]]"

function h.warn(msg)
	local utilsError = require("Module:UtilsError")
	utilsError.warn(msg)
end

function h.invalidColor(colorId)
	local TransclusionArguments = require("Module:Transclusion Arguments")
	h.warn(string.format("<code>%s</code> is not a valid color name. See [[Template:Color]] for a list of supported colors.", colorId))
	-- We only store invalid colors for performance reasons
	-- Storing all template usages seems to break pages that use the template a lot
	TransclusionArguments.store({
		module = "Module:Color",
		args = {
			[1] = colorId,
			[2] = text,
		},
		isValid = false,
	})
end

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("<code>text</code> parameter is required.")
		return text or "", CAT_INVALID_ARGS
	end
	if colorId == nil or colorId == "" then
		h.warn("<code>color</code> 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("<code>color</code> 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("zw-color")
		: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 <code>%s</code> instead.", colorData.deprecated)
		else
			action = "See [[Template:Color]] for a list of supported colors."
		end
		h.warn(string.format("Color <code>%s</code> is being discontinued. %s", colorId, action))
		return result, CAT_INVALID_COLOR
	end
	return result
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('<span id="%s">[[#%s|%s]]</span>', 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|Other]]</span>"),
			}
		})
		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,
					}
				},
				"<b><code>"..colorKey.."</code></b>",
				colorData.notes or ""
			})
		end
	end
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 [[Guidelines:Terminology|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"},
		}
	}
}

function p.Schemas()
	return {
		-- 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 [https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color hex color] (with preceding #) or a [https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient linear-gradient].",
							},
							{
								name = "isDefault",
								type = "boolean",
								desc = "Set this to <code>true</code> when the color is the game's default dialogue color. Used by [[Module:Cite]] to set the default text color for citations."
							},			
							{
								name = "defaultFor",
								type = "array",
								items = { type = "string" },
								desc = "A list of wiki page names referring to the characters and/or interfaces whose text uses the color by default. Used by [[Module:Cite]] to set the default text color for citations."
							},
							{
								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 <code>true</code>.",
							},
						}
					}
				}
			}
		}
	}
end

function p.Documentation()
	return {
		color = {
		params = {"colorName", "text"},
		returns = {
			"Colored text",
			"Error categories, or <code>nil</code> if no errors occurred",
		},
		cases = {
			{
				args = {"TMC Blue", "guy in green tights"},
				expect = {'<span class="zw-color" style="color:#37acbe">guy in green tights</span>', nil},
			},
			{
				args = {"notAColor", "foo"},
				expect = {"foo", "[[Category:Articles using invalid color names]]"},
			},
		},
	},
	}
end

return p