Module:Box-header
Appearance
![]() | This module is rated as ready for general use. It has reached a mature form and is thought to be relatively bug-free and ready for use wherever appropriate. It is ready to mention on help pages and other Wikipedia resources as an option for new users to learn. To reduce server load and bad output, it should be improved by sandbox testing rather than repeated trial-and-error editing. |
![]() | This module is subject to page protection. It is a highly visible module in use by a very large number of pages, or is substituted very frequently. Because vandalism or mistakes would affect many pages, and even trivial editing might cause substantial load on the servers, it is protected from editing. |
![]() | This Lua module is used on approximately 5,800 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. Consider discussing changes on the talk page before implementing them. |
![]() | This module depends on the following other modules: |
![]() | This module uses TemplateStyles: |
This module creates the header section for boxed content. It implements {{box-header}}. It is intended to mainly be used in portals, but can also be used elsewhere.
Usage
{{#invoke:Box-header|boxHeader}}
- For use in templates; calls
_boxHeader
with the parameters passed to the template as arguments.
{{#invoke:Box-header|_boxHeader|args}}
- For use in modules; constructs the box header (and the start of the box body). The args are the parameters accepted by Template:Box-header. (The output may need to be expanded, depending on the values in the args.)
{{#invoke:Box-header|autoColour}}
- For use in templates; calls
_autoColour
with the parameters passed to the template as arguments.
{{#invoke:Box-header|_autoColour|args}}
- For use in modules; calculates appropriate colours for the box header, and then constructs it using
_boxHeader
. The args are the parameters accepted by Template:Box-header colour – the same as for Template:Box-header, apart from those specifying colours and the title. (The output may need to be expanded, depending on the values in the args.)
See also
local p = {}
---------- Config data ----------
local namedColours = mw.loadData( 'Module:Sandbox/Evad37/Box-header/colours' )
local modes = {
lightest = { sat=0.10, val=1.00 },
light = { sat=0.15, val=0.95 },
normal = { sat=0.40, val=0.85 },
dark = { sat=0.90, val=0.70 },
darkest = { sat=1.00, val=0.45 },
content = { sat=0.04, val=1.00 }
}
local min_contrast_ratio_normal_text = 0.7
local min_contrast_ratio_large_text = 0.45
-- Template parameter names
-- Specify each as either a single string, or a table of strings (aliases)
-- Aliases are checked left-to-right, i.e. `{ "one", "two" }` is equivalent to using `{{{one| {{{two|}}} }}}` in a template
local parameterNames = {
['1'] = {'1', 1},
['2'] = {'2', 2},
['colour'] = {'colour', 'color'},
['mode'] = 'mode',
['watch'] = 'watch'
}
---------- Dependecies ----------
local colourContrastModule = require('Module:Color contrast')
local hex = require( 'luabit.hex' )
---------- Utility functions ----------
function getParameterValue(args, parameter)
suffix = suffix or ''
if type( parameterNames[parameter] ) ~= 'table' then
return args[parameterNames[parameter]]
end
for _i, parameterAlias in ipairs(parameterNames[parameter]) do
if args[parameterAlias] then
return args[parameterAlias]
end
end
return nil
end
function setCleanArgs(argsTable)
local cleanArgs = {}
for key, val in pairs(argsTable) do
if type(val) == 'string' then
val = val:match('^%s*(.-)%s*$')
if val ~= '' then
cleanArgs[key] = val
end
else
cleanArgs[key] = val
end
end
return cleanArgs
end
local function normaliseHexTriplet(hexString)
if not hexString then return nil end
local hexComponent = string.match(hexString, '^#(%x%x%x)$') or '^#(%x%x%x%x%x%x)$'
if hexComponent and #hexComponent == 6 then
return hexString
end
if hexComponent and #hexComponent == 3 then
local r = mw.ustring.rep(mw.ustring.sub(hexComponent, 1, 1), 2)
local b = mw.ustring.rep(mw.ustring.sub(hexComponent, 2, 2), 2)
local g = mw.ustring.rep(mw.ustring.sub(hexComponent, 3, 3), 2)
return '#' .. mw.ustring.upper(r .. g .. b)
end
return nil
end
---------- Conversions ----------
local function decimalToPaddedHex(number)
local prefixedHex = hex.to_hex(tonumber(number)) -- prefixed with '0x'
local padding = #prefixedHex == 3 and '0' or ''
return mw.ustring.gsub(prefixedHex, '0x', padding)
end
local function hexToDecimal(hexNumber)
return tonumber(hexNumber, 16)
end
local function RGBtoHexTriplet(R, G, B)
return '#' .. decimalToPaddedHex(R) .. decimalToPaddedHex(G) .. decimalToPaddedHex(B)
end
local function hexTripletToRGB(hexTriplet)
local R_hex, G_hex, B_hex = string.match(hexTriplet, '(%x%x)(%x%x)(%x%x)')
return hexToDecimal(R_hex), hexToDecimal(G_hex), hexToDecimal(B_hex)
end
local function HSVtoRGB(H, S, V) -- per [[HSL and HSV#Converting_to_RGB]]
local C = V * S
local H_prime = H / 60
local X = C * ( 1 - math.abs(math.fmod(H_prime, 2) - 1) )
local R1, G1, B1
if H_prime <= 1 then
R1 = C
G1 = X
B1 = 0
elseif H_prime <= 2 then
R1 = X
G1 = C
B1 = 0
elseif H_prime <= 3 then
R1 = 0
G1 = C
B1 = X
elseif H_prime <= 4 then
R1 = 0
G1 = X
B1 = C
elseif H_prime <= 5 then
R1 = X
G1 = 0
B1 = C
elseif H_prime <= 6 then
R1 = C
G1 = 0
B1 = X
end
local m = V - C
local R = R1 + m
local G = G1 + m
local B = B1 + m
local R_255 = math.floor(R*255)
local G_255 = math.floor(G*255)
local B_255 = math.floor(B*255)
return R_255, G_255, B_255
end
local function RGBtoHue(R_255, G_255, B_255) -- per [[HSL and HSV#Hue and chroma]]
local R = R_255/255
local G = G_255/255
local B = B_255/255
local M = math.max(R, G, B)
local m = math.min(R, G, B)
local C = M - m
local H_prime
if C == 0 then
return null
elseif M == R then
H_prime = math.fmod((G - B)/C, 6)
elseif M == G then
H_prime = (B - R)/C + 2
elseif M == B then
H_prime = (R - G)/C + 4
end
local H = 60 * H_prime
return H
end
local function nameToHexTriplet(name)
if not name then return nil end
local codename = mw.ustring.gsub(mw.ustring.lower(name), ' ', '')
return namedColours[codename]
end
---------- Choose colours ----------
local function calculateColours(H, S, V, minContrast)
local bgColour = RGBtoHexTriplet(HSVtoRGB(H, S, V))
local textColour = colourContrastModule._greatercontrast({bgColour})
local contrast = colourContrastModule._ratio({ bgColour, textColour })
if contrast >= minContrast then
return bgColour, textColour
elseif textColour == '#FFFFFF' then
-- make the background darker and slightly increase the saturation
return calculateColours(H, math.min(1, S+0.01), math.max(0, V-0.03), minContrast)
else
-- make the background lighter and slightly decrease the saturation
return calculateColours(H, math.max(0, S-0.01), math.min(1, V+0.03), minContrast)
end
end
local function makeColours(hue, modeName)
local mode = modes[modeName]
if not hue then -- grey
mode.sat = 0
hue = 0
end
local border = RGBtoHexTriplet(HSVtoRGB(hue, 0.15, 0.75))
local titleBackground, titleForeground = calculateColours(hue, mode.sat, mode.val, min_contrast_ratio_large_text)
local contentBackground, contentForeground = calculateColours(hue, modes.content.sat, modes.content.val, min_contrast_ratio_normal_text)
return border, titleForeground, titleBackground, contentForeground, contentBackground
end
local function findHue(colour)
local colourAsNumber = tonumber(colour)
if colourAsNumber and ( -1 < colourAsNumber ) and ( colourAsNumber < 360) then
return colourAsNumber
end
local colourAsHexTriplet = normaliseHexTriplet(colour) or nameToHexTriplet(colour)
if colourAsHexTriplet then
return RGBtoHue(hexTripletToRGB(colourAsHexTriplet))
end
return null
end
local function normaliseDescription(description)
if not description or not modes[mw.ustring.lower(description)] then
return 'normal'
end
return mw.ustring.lower(description)
end
---------- Build output ----------
local function boxHeader(args)
-- TODO
return nil
end
local function boxBody(args)
-- TODO
return nil
end
---------- Main ----------
-- Entry point for templates
function p.main(frame)
local parent = frame.getParent(frame)
local boxTemplateName, boxTemplateArgs = p._main(parent.args)
return frame:expandTemplate{ title = boxTemplateName, args = boxTemplateArgs }
end
-- Entry point for modules
function p._main(_args)
local args = setCleanArgs(_args)
local hue = findHue(getParameterValue(args, 'colour'))
local mode = normaliseDescription(getParameterValue(args, 'mode'))
local border, titleForeground, titleBackground, contentForeground, contentBackground = makeColours(hue, mode)
local boxTemplateName = getParameterValue(args, 'watch') and 'Box-header' .. getParameterValue(args, 'watch') or 'Box-header'
local boxTemplateArgs = {
title = getParameterValue(args, '1') or '',
editpage = getParameterValue(args, '2') or '',
noedit = getParameterValue(args, '2') and '' or 'yes',
border = border,
titleforeground = titleForeground,
titlebackground = titleBackground,
foreground = contentForeground,
background = contentBackground
}
return boxTemplateName, boxTemplateArgs
end
return p