Bước tới nội dung

Mô đun:Box-header

Bách khoa toàn thư mở Wikipedia
local getArgs = require('Mô đun:Arguments').getArgslocal p = {}---------- Config data ----------local namedColours = mw.loadData( 'Mô đun: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:1local 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 templatelocal parameterAliases = {	['1'] = 1,	['2'] = 2,	['colour'] = 'color'}---------- Dependecies ----------local colourContrastModule = require('Mô đun:Color contrast')local hex = require( 'luabit.hex' )---------- Utility functions ----------local function getParam(args, parameter)	if args[parameter] then		return args[parameter]	end	local aliases = parameterAliases[parameter]	if not aliases then		return nil	end	if type(aliases) ~= 'table' then		return args[aliases]	end	for _, alias in ipairs(aliases) do		if args[alias] then			return args[alias]		end	end	return nilendlocal 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 cleanArgsend-- 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		merged[key] = val	end	for key, val in pairs(second) do		merged[key] = val	end	return mergedendlocal function toOpenTagString(selfClosedHtmlObject)	local closedTagString = tostring(selfClosedHtmlObject)	local openTagString = mw.ustring.gsub(closedTagString, ' />$', '>')	return openTagStringendlocal 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 nilend---------- 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)endlocal function hexToDecimal(hexNumber)	return tonumber(hexNumber, 16)endlocal function RGBtoHexTriplet(R, G, B)	return '#' .. decimalToPaddedHex(R) .. decimalToPaddedHex(G) .. decimalToPaddedHex(B)endlocal 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)endlocal 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_255endlocal 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 Hendlocal 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.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)	endendlocal function makeColours(hue, modeName)	local mode = modes[modeName]	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, contentBackgroundendlocal 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 nullendlocal function normaliseMode(mode)	if not mode or not modes[mw.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)endlocal 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)endlocal 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('sửa')	local linktext = tostring(tag)	local linktarget = tostring(mw.uri.fullUrl(page, {action='edit', section=getParam(args, 'section')}))	return '[' .. linktarget  .. ' ' .. linktext .. ']&nbsp;'endlocal function boxHeaderViewLink(args)	local style = {		color = getParam(args, 'titleforeground') or '#000'	}	local tag = mw.html.create('span')		:css(style)		:wikitext('xem')	local linktext = tostring(tag)	local linktarget = ':' .. getParam(args, 'viewpage')	return "<b>·</b>&nbsp;[[" .. linktarget  .. '|' .. linktext .. ']]&nbsp;'endlocal 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 = parts[1]			local val = parts[2]			if prop and val then				tag:css(prop, val)			end		end	end	return tostring(tag)endlocal 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)endlocal 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 '[[Thể loại:Box-header có độ tương phản tựa đề không đủ]][[Thể loại:Box-header có độ tương phản vật thể không đủ]]'	elseif insufficientTitleContrast then		return '[[Thể loại:Box-header có độ tương phản tựa đề không đủ]]'	elseif insufficientBodyContrast then		return '[[Thể loại:Box-header có độ tương phản vật thể không đủ]]'	else		return ''	endend---------- 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 outputend-- 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 outputend-- 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