Permanently protected module
From Wikipedia, the free encyclopedia


local getArgs = require('Module:Arguments').getArgs



local p = {}

---------- Config data ----------

local namedColours = mw.loadData( 'Module: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 },

	grey     = { sat=0.00 }

}

local min_contrast_ratio_normal_text = 7  -- i.e 7:1

local min_contrast_ratio_large_text  = 4.5  -- i.e. 4.5:1



-- Template parameter aliases

--   Specify each as either a single value, or a table of values

--   Aliases are checked left-to-right, i.e. `['one'] = { 'two', 'three' }` is equivalent to using `{{{one| {{{two| {{{three|}}} }}} }}}` in a template

local parameterAliases = {

	'1' = 1,

	'2' = 2,

	'colour' = 'color'

}



---------- Dependecies ----------

local colourContrastModule = require('Module:Color contrast')

local hex = require( 'luabit.hex' )



---------- Utility functions ----------

local function getParam(args, parameter)

	if argsparameter then

		return argsparameter

	end

	local aliases = parameterAliasesparameter

	if not aliases then

		return nil

	end

	if type(aliases) ~= 'table' then

		return argsaliases

	end

	for _, alias in ipairs(aliases) do

		if argsalias then

			return argsalias

		end

	end

	return nil

end



local 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

				cleanArgskey = val

			end

		else

			cleanArgskey = val

		end

	end

	return cleanArgs

end



-- Merge two tables into a new table. If the are any duplicate keys, the values from the second overwrite the values from the first.

local function mergeTables(first, second)

	local merged = {}

	for key, val in pairs(first) do

		mergedkey = val

	end

	for key, val in pairs(second) do

		mergedkey = val

	end

	return merged

end



local function toOpenTagString(selfClosedHtmlObject)

	local closedTagString = tostring(selfClosedHtmlObject)

	local openTagString = mw.ustring.gsub(closedTagString, ' />$', '>')

	return openTagString

end



local function normaliseHexTriplet(hexString)

	if not hexString then return nil end

	local hexComponent = mw.ustring.match(hexString, '^#(%x%x%x)$') or mw.ustring.match(hexString, '^#(%x%x%x%x%x%x)$')

	if hexComponent and #hexComponent == 6 then

		return mw.ustring.upper(hexString)

	end

	if hexComponent and #hexComponent == 3 then

		local r = mw.ustring.rep(mw.ustring.sub(hexComponent, 1, 1), 2)

		local g = mw.ustring.rep(mw.ustring.sub(hexComponent, 2, 2), 2)

		local b = 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), 6) -- adding six before taking mod ensures positive value

	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 namedColourscodename

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.005), 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.005), math.min(1, V+0.03), minContrast)

	end

end



local function makeColours(hue, modeName)

	local mode = modesmodeName

	local isGrey = not(hue)

	if isGrey then hue = 0 end



	local borderSat = isGrey and modes.grey.sat or 0.15

	local border = RGBtoHexTriplet(HSVtoRGB(hue, borderSat, 0.75))



	local titleSat = isGrey and modes.grey.sat or mode.sat

	local titleBackground, titleForeground = calculateColours(hue, titleSat, mode.val, min_contrast_ratio_large_text)



	local contentSat = isGrey and modes.grey.sat or modes.content.sat

	local contentBackground, contentForeground = calculateColours(hue, contentSat, 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 normaliseMode(mode)

	if not mode or not modesmw.ustring.lower(mode)] or mw.ustring.lower(mode) == 'grey' then

		return 'normal'

	end

	return mw.ustring.lower(mode)

end

---------- Build output ----------

local function boxHeaderOuter(args)

	local baseStyle = {

		clear = 'both',

		'box-sizing' = 'border-box',

		border = ( getParam(args, 'border-type') or 'solid' ) .. ' ' .. ( getParam(args, 'titleborder') or getParam(args, 'border') or '#ababab' ),

		background = getParam(args, 'titlebackground') or '#bcbcbc',

		color = getParam(args, 'titleforeground') or '#000',

		padding = getParam(args, 'padding') or '.1em',

		'text-align' = getParam(args, 'title-align') or 'center',

		'font-family' = getParam(args, 'font-family') or 'sans-serif',

		'font-size' = getParam(args, 'titlefont-size') or '100%',

		'margin-bottom' = '0px',

	}





	local tag = mw.html.create('div', {selfClosing = true})

		:addClass('box-header-title-container')

		:addClass('flex-columns-noflex')

		:css(baseStyle)

		:css('border-width', ( getParam(args, 'border-top') or getParam(args, 'border-width') or '1' ) .. 'px ' .. ( getParam(args, 'border-width') or '1' ) .. 'px 0')

		:css('padding-top', getParam(args, 'padding-top') or '.1em')

		:css('padding-left', getParam(args, 'padding-left') or '.1em')

		:css('padding-right', getParam(args, 'padding-right') or '.1em')

		:css('padding-bottom', getParam(args, 'padding-bottom') or '.1em')

		:css('moz-border-radius', getParam(args, 'title-border-radius') or '0')

		:css('webkit-border-radius', getParam(args, 'title-border-radius') or '0')

		:css('border-radius', getParam(args, 'title-border-radius') or '0')

	return toOpenTagString(tag)

end



local function boxHeaderTopLinks(args)

	local style = {

		float = 'right',

		'margin-bottom' = '.1em',

		'font-size' = getParam(args, 'font-size') or '80%',

		color = getParam(args, 'titleforeground') or '#000'

	}

	local tag = mw.html.create('div', {selfClosing = true})

		:addClass('plainlinks noprint' )

		:css(style)

	return toOpenTagString(tag)

end



local function boxHeaderEditLink(args)

	local page = getParam(args, 'editpage')

	if not page or page == '{{{2}}}'

	then

		return ''

	end

	local style = {

		color = getParam(args, 'titleforeground') or '#000'

	}

	local tag = mw.html.create('span')

		:css(style)

		:wikitext('edit')

	local linktext = tostring(tag)

	local linktarget = tostring(mw.uri.fullUrl(page, {action='edit', section=getParam(args, 'section')}))

	return '[' .. linktarget  .. ' ' .. linktext .. ']&nbsp;'

end



local function boxHeaderViewLink(args)

	local style = {

		color = getParam(args, 'titleforeground') or '#000'

	}

	local tag = mw.html.create('span')

		:css(style)

		:wikitext('view')

	local linktext = tostring(tag)

	local linktarget = ':' .. getParam(args, 'viewpage')

	return "<b>·</b>&nbsp;[[" .. linktarget  .. '|' .. linktext .. ']]&nbsp;'

end



local function boxHeaderTitle(args)

	local baseStyle = {

		'font-family' = getParam(args, 'title-font-family') or 'sans-serif',

		'font-size' = getParam(args, 'title-font-size') or '100%',

		'font-weight' = getParam(args, 'title-font-weight') or 'bold',

		border = 'none',

		margin = '0',

		padding = '0',

		color = getParam(args, 'titleforeground') or '#000';

	}

	local tagName = getParam(args, 'SPAN') and 'span' or 'h2'

	local tag = mw.html.create(tagName)

		:css(baseStyle)

		:css('padding-bottom', '.1em')

		:wikitext(getParam(args, 'title'))

	if getParam(args, 'extra') then

		local rules = mw.text.split(getParam(args, 'extra'), ';', true)

		for _, rule in pairs(rules) do

			local parts = mw.text.split(rule, ':', true)

			local prop = parts1

			local val = parts2

			if prop and val then

				tag:css(prop, val)

			end

		end

	end

	return tostring(tag)

end



local function boxBody(args)

	local baseStyle = {

		'box-sizing' = 'border-box',

		border = ( getParam(args, 'border-width') or '1' ) .. 'px solid ' .. ( getParam(args, 'border') or '#ababab'),

		'vertical-align' = 'top';

		background = getParam(args, 'background') or '#fefeef',

		opacity = getParam(args, 'background-opacity') or '1',

		color = getParam(args, 'foreground') or '#000',

		'text-align' = getParam(args, 'text-align') or 'left',

		margin = '0 0 10px',

		padding = getParam(args, 'padding') or '1em',

	}

	local tag = mw.html.create('div', {selfClosing = true})

		:css(baseStyle)

		:css('border-top-width', ( getParam(args, 'border-top') or '1' ) .. 'px')

		:css('padding-top', getParam(args, 'padding-top') or '.3em')

		:css('border-radius', getParam(args, 'border-radius') or '0')

	return toOpenTagString(tag)

end



local function contrastCategories(args)

	local cats = ''



	local titleText = nameToHexTriplet(getParam(args, 'titleforeground')) or normaliseHexTriplet(getParam(args, 'titleforeground')) or '#000000'

	local titleBackground = nameToHexTriplet(getParam(args, 'titlebackground')) or normaliseHexTriplet(getParam(args, 'titlebackground')) or '#bcbcbc'

	local titleContrast = colourContrastModule._ratio({titleBackground, titleText})

	local insufficientTitleContrast = type(titleContrast) == 'number' and ( titleContrast < min_contrast_ratio_large_text )



	local bodyText = nameToHexTriplet(getParam(args, 'foreground')) or normaliseHexTriplet(getParam(args, 'foreground')) or '#000000'

	local bodyBackground = nameToHexTriplet(getParam(args, 'background')) or normaliseHexTriplet(getParam(args, 'background')) or '#fefeef'

	local bodyContrast =  colourContrastModule._ratio({bodyBackground, bodyText})

	local insufficientBodyContrast = type(bodyContrast) == 'number' and ( bodyContrast < min_contrast_ratio_normal_text )



	if insufficientTitleContrast and insufficientBodyContrast then

		return '[[Category:Box-header with insufficient title contrast]][[Category:Box-header with insufficient body contrast]]'

	elseif insufficientTitleContrast then

		return '[[Category:Box-header with insufficient title contrast]]'

	elseif insufficientBodyContrast then

		return '[[Category:Box-header with insufficient body contrast]]'

	else

		return ''

	end

end



---------- Main functions / entry points ----------



-- Entry point for templates (manually-specified colours)

function p.boxHeader(frame)

	local args = getArgs(frame)

	local page = args.editpage

	if not args.editpage or args.editpage == '' then

		page = mw.title.getCurrentTitle().prefixedText

	end

	local output = p._boxHeader(args, page)

	if mw.ustring.find(output, '{') then

		return frame:preprocess(output)

	end

	return output

end



-- Entry point for modules (manually-specified colours)

function p._boxHeader(_args, page)

	local args = setCleanArgs(_args)

	if page and not args.editpage then

		args.editpage = page

	end

	if not args.title then

		args.title = '{{{title}}}'

	end

	local output = {}

	table.insert(output, boxHeaderOuter(args))

	if not getParam(args, 'EDITLINK') then

		table.insert(output, boxHeaderTopLinks(args))

		if not getParam(args, 'noedit') then

			table.insert(output, boxHeaderEditLink(args))

		end

		if getParam(args, 'viewpage') then

			table.insert(output, boxHeaderViewLink(args))

		end

		if getParam(args, 'top') then

			table.insert(output, getParam(args, 'top') .. '&nbsp;')

		end

		table.insert(output, '</div>')

	end

	table.insert(output, boxHeaderTitle(args))

	table.insert(output, '</div>')

	table.insert(output, boxBody(args))

	if not getParam(args, 'TOC') then

		table.insert(output, '__NOTOC__')

	end

	if not getParam(args, 'EDIT') then

		table.insert(output, '__NOEDITSECTION__')

	end

	table.insert(output, contrastCategories(args))



	return table.concat(output)

end



-- Entry point for templates (automatically calculated colours)

function p.autoColour(frame)

	local args = getArgs(frame)

	local colourParam = getParam(args, 'colour')

	local generatedColour = nil

	if not colourParam or colourParam == '' then

		-- convert the root page name into a number and use that

		local root = mw.title.getCurrentTitle().rootPageTitle.prefixedText

		local rootStart = mw.ustring.sub(root, 1, 12)

		local digitsFromRootStart = mw.ustring.gsub(rootStart, ".", function(s) return math.fmod(string.byte(s, 2) or string.byte(s, 1), 10) end)

		local numberFromRoot = tonumber(digitsFromRootStart, 10)

		generatedColour = math.fmod(numberFromRoot, 360)

	end

	local output = p._autoColour(args, generatedColour)

	if mw.ustring.find(output, '{') then

		return frame:preprocess(output)

	end

	return output

end



-- Entry point for modules (automatically calculated colours)

function p._autoColour(_args, generatedColour)

	local args = setCleanArgs(_args)

	local hue = generatedColour or findHue(getParam(args, 'colour'))

	local mode = normaliseMode(getParam(args, 'mode'))

	local border, titleForeground, titleBackground, contentForeground, contentBackground = makeColours(hue, mode)

	local boxTemplateArgs = mergeTables(args, {

		title = getParam(args, '1') or '{{{1}}}',

		editpage = getParam(args, '2') or '',

		noedit = getParam(args, '2') and '' or 'yes',

		border = border,

		titleforeground = titleForeground,

		titlebackground = titleBackground,

		foreground = contentForeground,

		background = contentBackground

	})

	return p._boxHeader(boxTemplateArgs)

end

	

return p
Permanently protected module
From Wikipedia, the free encyclopedia


local getArgs = require('Module:Arguments').getArgs



local p = {}

---------- Config data ----------

local namedColours = mw.loadData( 'Module: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 },

	grey     = { sat=0.00 }

}

local min_contrast_ratio_normal_text = 7  -- i.e 7:1

local min_contrast_ratio_large_text  = 4.5  -- i.e. 4.5:1



-- Template parameter aliases

--   Specify each as either a single value, or a table of values

--   Aliases are checked left-to-right, i.e. `['one'] = { 'two', 'three' }` is equivalent to using `{{{one| {{{two| {{{three|}}} }}} }}}` in a template

local parameterAliases = {

	'1' = 1,

	'2' = 2,

	'colour' = 'color'

}



---------- Dependecies ----------

local colourContrastModule = require('Module:Color contrast')

local hex = require( 'luabit.hex' )



---------- Utility functions ----------

local function getParam(args, parameter)

	if argsparameter then

		return argsparameter

	end

	local aliases = parameterAliasesparameter

	if not aliases then

		return nil

	end

	if type(aliases) ~= 'table' then

		return argsaliases

	end

	for _, alias in ipairs(aliases) do

		if argsalias then

			return argsalias

		end

	end

	return nil

end



local 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

				cleanArgskey = val

			end

		else

			cleanArgskey = val

		end

	end

	return cleanArgs

end



-- Merge two tables into a new table. If the are any duplicate keys, the values from the second overwrite the values from the first.

local function mergeTables(first, second)

	local merged = {}

	for key, val in pairs(first) do

		mergedkey = val

	end

	for key, val in pairs(second) do

		mergedkey = val

	end

	return merged

end



local function toOpenTagString(selfClosedHtmlObject)

	local closedTagString = tostring(selfClosedHtmlObject)

	local openTagString = mw.ustring.gsub(closedTagString, ' />$', '>')

	return openTagString

end



local function normaliseHexTriplet(hexString)

	if not hexString then return nil end

	local hexComponent = mw.ustring.match(hexString, '^#(%x%x%x)$') or mw.ustring.match(hexString, '^#(%x%x%x%x%x%x)$')

	if hexComponent and #hexComponent == 6 then

		return mw.ustring.upper(hexString)

	end

	if hexComponent and #hexComponent == 3 then

		local r = mw.ustring.rep(mw.ustring.sub(hexComponent, 1, 1), 2)

		local g = mw.ustring.rep(mw.ustring.sub(hexComponent, 2, 2), 2)

		local b = 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), 6) -- adding six before taking mod ensures positive value

	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 namedColourscodename

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.005), 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.005), math.min(1, V+0.03), minContrast)

	end

end



local function makeColours(hue, modeName)

	local mode = modesmodeName

	local isGrey = not(hue)

	if isGrey then hue = 0 end



	local borderSat = isGrey and modes.grey.sat or 0.15

	local border = RGBtoHexTriplet(HSVtoRGB(hue, borderSat, 0.75))



	local titleSat = isGrey and modes.grey.sat or mode.sat

	local titleBackground, titleForeground = calculateColours(hue, titleSat, mode.val, min_contrast_ratio_large_text)



	local contentSat = isGrey and modes.grey.sat or modes.content.sat

	local contentBackground, contentForeground = calculateColours(hue, contentSat, 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 normaliseMode(mode)

	if not mode or not modesmw.ustring.lower(mode)] or mw.ustring.lower(mode) == 'grey' then

		return 'normal'

	end

	return mw.ustring.lower(mode)

end

---------- Build output ----------

local function boxHeaderOuter(args)

	local baseStyle = {

		clear = 'both',

		'box-sizing' = 'border-box',

		border = ( getParam(args, 'border-type') or 'solid' ) .. ' ' .. ( getParam(args, 'titleborder') or getParam(args, 'border') or '#ababab' ),

		background = getParam(args, 'titlebackground') or '#bcbcbc',

		color = getParam(args, 'titleforeground') or '#000',

		padding = getParam(args, 'padding') or '.1em',

		'text-align' = getParam(args, 'title-align') or 'center',

		'font-family' = getParam(args, 'font-family') or 'sans-serif',

		'font-size' = getParam(args, 'titlefont-size') or '100%',

		'margin-bottom' = '0px',

	}





	local tag = mw.html.create('div', {selfClosing = true})

		:addClass('box-header-title-container')

		:addClass('flex-columns-noflex')

		:css(baseStyle)

		:css('border-width', ( getParam(args, 'border-top') or getParam(args, 'border-width') or '1' ) .. 'px ' .. ( getParam(args, 'border-width') or '1' ) .. 'px 0')

		:css('padding-top', getParam(args, 'padding-top') or '.1em')

		:css('padding-left', getParam(args, 'padding-left') or '.1em')

		:css('padding-right', getParam(args, 'padding-right') or '.1em')

		:css('padding-bottom', getParam(args, 'padding-bottom') or '.1em')

		:css('moz-border-radius', getParam(args, 'title-border-radius') or '0')

		:css('webkit-border-radius', getParam(args, 'title-border-radius') or '0')

		:css('border-radius', getParam(args, 'title-border-radius') or '0')

	return toOpenTagString(tag)

end



local function boxHeaderTopLinks(args)

	local style = {

		float = 'right',

		'margin-bottom' = '.1em',

		'font-size' = getParam(args, 'font-size') or '80%',

		color = getParam(args, 'titleforeground') or '#000'

	}

	local tag = mw.html.create('div', {selfClosing = true})

		:addClass('plainlinks noprint' )

		:css(style)

	return toOpenTagString(tag)

end



local function boxHeaderEditLink(args)

	local page = getParam(args, 'editpage')

	if not page or page == '{{{2}}}'

	then

		return ''

	end

	local style = {

		color = getParam(args, 'titleforeground') or '#000'

	}

	local tag = mw.html.create('span')

		:css(style)

		:wikitext('edit')

	local linktext = tostring(tag)

	local linktarget = tostring(mw.uri.fullUrl(page, {action='edit', section=getParam(args, 'section')}))

	return '[' .. linktarget  .. ' ' .. linktext .. ']&nbsp;'

end



local function boxHeaderViewLink(args)

	local style = {

		color = getParam(args, 'titleforeground') or '#000'

	}

	local tag = mw.html.create('span')

		:css(style)

		:wikitext('view')

	local linktext = tostring(tag)

	local linktarget = ':' .. getParam(args, 'viewpage')

	return "<b>·</b>&nbsp;[[" .. linktarget  .. '|' .. linktext .. ']]&nbsp;'

end



local function boxHeaderTitle(args)

	local baseStyle = {

		'font-family' = getParam(args, 'title-font-family') or 'sans-serif',

		'font-size' = getParam(args, 'title-font-size') or '100%',

		'font-weight' = getParam(args, 'title-font-weight') or 'bold',

		border = 'none',

		margin = '0',

		padding = '0',

		color = getParam(args, 'titleforeground') or '#000';

	}

	local tagName = getParam(args, 'SPAN') and 'span' or 'h2'

	local tag = mw.html.create(tagName)

		:css(baseStyle)

		:css('padding-bottom', '.1em')

		:wikitext(getParam(args, 'title'))

	if getParam(args, 'extra') then

		local rules = mw.text.split(getParam(args, 'extra'), ';', true)

		for _, rule in pairs(rules) do

			local parts = mw.text.split(rule, ':', true)

			local prop = parts1

			local val = parts2

			if prop and val then

				tag:css(prop, val)

			end

		end

	end

	return tostring(tag)

end



local function boxBody(args)

	local baseStyle = {

		'box-sizing' = 'border-box',

		border = ( getParam(args, 'border-width') or '1' ) .. 'px solid ' .. ( getParam(args, 'border') or '#ababab'),

		'vertical-align' = 'top';

		background = getParam(args, 'background') or '#fefeef',

		opacity = getParam(args, 'background-opacity') or '1',

		color = getParam(args, 'foreground') or '#000',

		'text-align' = getParam(args, 'text-align') or 'left',

		margin = '0 0 10px',

		padding = getParam(args, 'padding') or '1em',

	}

	local tag = mw.html.create('div', {selfClosing = true})

		:css(baseStyle)

		:css('border-top-width', ( getParam(args, 'border-top') or '1' ) .. 'px')

		:css('padding-top', getParam(args, 'padding-top') or '.3em')

		:css('border-radius', getParam(args, 'border-radius') or '0')

	return toOpenTagString(tag)

end



local function contrastCategories(args)

	local cats = ''



	local titleText = nameToHexTriplet(getParam(args, 'titleforeground')) or normaliseHexTriplet(getParam(args, 'titleforeground')) or '#000000'

	local titleBackground = nameToHexTriplet(getParam(args, 'titlebackground')) or normaliseHexTriplet(getParam(args, 'titlebackground')) or '#bcbcbc'

	local titleContrast = colourContrastModule._ratio({titleBackground, titleText})

	local insufficientTitleContrast = type(titleContrast) == 'number' and ( titleContrast < min_contrast_ratio_large_text )



	local bodyText = nameToHexTriplet(getParam(args, 'foreground')) or normaliseHexTriplet(getParam(args, 'foreground')) or '#000000'

	local bodyBackground = nameToHexTriplet(getParam(args, 'background')) or normaliseHexTriplet(getParam(args, 'background')) or '#fefeef'

	local bodyContrast =  colourContrastModule._ratio({bodyBackground, bodyText})

	local insufficientBodyContrast = type(bodyContrast) == 'number' and ( bodyContrast < min_contrast_ratio_normal_text )



	if insufficientTitleContrast and insufficientBodyContrast then

		return '[[Category:Box-header with insufficient title contrast]][[Category:Box-header with insufficient body contrast]]'

	elseif insufficientTitleContrast then

		return '[[Category:Box-header with insufficient title contrast]]'

	elseif insufficientBodyContrast then

		return '[[Category:Box-header with insufficient body contrast]]'

	else

		return ''

	end

end



---------- Main functions / entry points ----------



-- Entry point for templates (manually-specified colours)

function p.boxHeader(frame)

	local args = getArgs(frame)

	local page = args.editpage

	if not args.editpage or args.editpage == '' then

		page = mw.title.getCurrentTitle().prefixedText

	end

	local output = p._boxHeader(args, page)

	if mw.ustring.find(output, '{') then

		return frame:preprocess(output)

	end

	return output

end



-- Entry point for modules (manually-specified colours)

function p._boxHeader(_args, page)

	local args = setCleanArgs(_args)

	if page and not args.editpage then

		args.editpage = page

	end

	if not args.title then

		args.title = '{{{title}}}'

	end

	local output = {}

	table.insert(output, boxHeaderOuter(args))

	if not getParam(args, 'EDITLINK') then

		table.insert(output, boxHeaderTopLinks(args))

		if not getParam(args, 'noedit') then

			table.insert(output, boxHeaderEditLink(args))

		end

		if getParam(args, 'viewpage') then

			table.insert(output, boxHeaderViewLink(args))

		end

		if getParam(args, 'top') then

			table.insert(output, getParam(args, 'top') .. '&nbsp;')

		end

		table.insert(output, '</div>')

	end

	table.insert(output, boxHeaderTitle(args))

	table.insert(output, '</div>')

	table.insert(output, boxBody(args))

	if not getParam(args, 'TOC') then

		table.insert(output, '__NOTOC__')

	end

	if not getParam(args, 'EDIT') then

		table.insert(output, '__NOEDITSECTION__')

	end

	table.insert(output, contrastCategories(args))



	return table.concat(output)

end



-- Entry point for templates (automatically calculated colours)

function p.autoColour(frame)

	local args = getArgs(frame)

	local colourParam = getParam(args, 'colour')

	local generatedColour = nil

	if not colourParam or colourParam == '' then

		-- convert the root page name into a number and use that

		local root = mw.title.getCurrentTitle().rootPageTitle.prefixedText

		local rootStart = mw.ustring.sub(root, 1, 12)

		local digitsFromRootStart = mw.ustring.gsub(rootStart, ".", function(s) return math.fmod(string.byte(s, 2) or string.byte(s, 1), 10) end)

		local numberFromRoot = tonumber(digitsFromRootStart, 10)

		generatedColour = math.fmod(numberFromRoot, 360)

	end

	local output = p._autoColour(args, generatedColour)

	if mw.ustring.find(output, '{') then

		return frame:preprocess(output)

	end

	return output

end



-- Entry point for modules (automatically calculated colours)

function p._autoColour(_args, generatedColour)

	local args = setCleanArgs(_args)

	local hue = generatedColour or findHue(getParam(args, 'colour'))

	local mode = normaliseMode(getParam(args, 'mode'))

	local border, titleForeground, titleBackground, contentForeground, contentBackground = makeColours(hue, mode)

	local boxTemplateArgs = mergeTables(args, {

		title = getParam(args, '1') or '{{{1}}}',

		editpage = getParam(args, '2') or '',

		noedit = getParam(args, '2') and '' or 'yes',

		border = border,

		titleforeground = titleForeground,

		titlebackground = titleBackground,

		foreground = contentForeground,

		background = contentBackground

	})

	return p._boxHeader(boxTemplateArgs)

end

	

return p

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook