Модуль:Box-header![]()
Цей модуль створює заголовок розділу для вмісту контейнера. Він впроваджує {{box-header}}. Основним призначенням є використання в порталах, але також може використовуватися в інших місцях. Використання
See also
local getArgs = require('Модуль:Arguments').getArgs
local p = {}
---------- Config data ----------
local namedColours = mw.loadData( 'Модуль: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',
['hidden'] = 'hide'
}
---------- Dependecies ----------
local colourContrastModule = require('Модуль: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 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
cleanArgs[key] = val
end
else
cleanArgs[key] = 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
merged[key] = val
end
for key, val in pairs(second) do
merged[key] = 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 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)
end
end
local 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, 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 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 boxCollapsibleDiv(args)
local tag = mw.html.create('div', {selfClosing = true})
:addClass('mw-collapsible' .. (getParam(args, 'hidden') and 'mw-collapsed' or ''))
return toOpenTagString(tag)
end
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 boxHeaderCollapsibleButton()
local tag = mw.html.create('span')
:addClass('mw-collapsible-toggle-placeholder')
return tostring(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('редагувати')
local linktext = tostring(tag)
local linktarget = tostring(mw.uri.fullUrl(page, {action='edit', section=getParam(args, 'section')}))
return '[' .. linktarget .. ' ' .. linktext .. '] '
end
local function boxHeaderViewLink(args)
local style = {
color = getParam(args, 'titleforeground') or '#000'
}
local tag = mw.html.create('span')
:css(style)
:wikitext('переглянути')
local linktext = tostring(tag)
local linktarget = ':' .. getParam(args, 'viewpage')
return "<b>·</b> [[" .. linktarget .. '|' .. linktext .. ']] '
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 = parts[1]
local val = parts[2]
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})
:addClass(args.collapsible and 'mw-collapsible-content' or '')
: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 '[[Категорія:Box-header із недостатнім контрастом title]][[Категорія:Box-header із недостатнім контрастом body]]'
elseif insufficientTitleContrast then
return '[[Категорія:Box-header із недостатнім контрастом title]]'
elseif insufficientBodyContrast then
return '[[Категорія:Box-header із недостатнім контрастом body]]'
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
args.collapsible = getParam(args, 'collapsible')
local output = {}
if args.collapsible then
table.insert(output, boxCollapsibleDiv(args))
end
table.insert(output, boxHeaderOuter(args))
if args.collapsible then
table.insert(output, boxHeaderCollapsibleButton())
end
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') .. ' ')
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
|
Index:
pl ar de en es fr it arz nl ja pt ceb sv uk vi war zh ru af ast az bg zh-min-nan bn be ca cs cy da et el eo eu fa gl ko hi hr id he ka la lv lt hu mk ms min no nn ce uz kk ro simple sk sl sr sh fi ta tt th tg azb tr ur zh-yue hy my ace als am an hyw ban bjn map-bms ba be-tarask bcl bpy bar bs br cv nv eml hif fo fy ga gd gu hak ha hsb io ig ilo ia ie os is jv kn ht ku ckb ky mrj lb lij li lmo mai mg ml zh-classical mr xmf mzn cdo mn nap new ne frr oc mhr or as pa pnb ps pms nds crh qu sa sah sco sq scn si sd szl su sw tl shn te bug vec vo wa wuu yi yo diq bat-smg zu lad kbd ang smn ab roa-rup frp arc gn av ay bh bi bo bxr cbk-zam co za dag ary se pdc dv dsb myv ext fur gv gag inh ki glk gan guw xal haw rw kbp pam csb kw km kv koi kg gom ks gcr lo lbe ltg lez nia ln jbo lg mt mi tw mwl mdf mnw nqo fj nah na nds-nl nrm nov om pi pag pap pfl pcd krc kaa ksh rm rue sm sat sc trv stq nso sn cu so srn kab roa-tara tet tpi to chr tum tk tyv udm ug vep fiu-vro vls wo xh zea ty ak bm ch ny ee ff got iu ik kl mad cr pih ami pwn pnt dz rmy rn sg st tn ss ti din chy ts kcg ve
Portal di Ensiklopedia Dunia