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 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
localgetArgs=require('Module:Arguments').getArgslocalp={}---------- Config data ----------localnamedColours=mw.loadData('Module:Box-header/colours')localmodes={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}}localmin_contrast_ratio_normal_text=7-- i.e 7:1localmin_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 templatelocalparameterAliases={['1']=1,['2']=2,['colour']='color'}---------- Dependecies ----------localcolourContrastModule=require('Module:Color contrast')localhex=require('luabit.hex')---------- Utility functions ----------localfunctiongetParam(args,parameter)ifargs[parameter]thenreturnargs[parameter]endlocalaliases=parameterAliases[parameter]ifnotaliasesthenreturnnilendiftype(aliases)~='table'thenreturnargs[aliases]endfor_,aliasinipairs(aliases)doifargs[alias]thenreturnargs[alias]endendreturnnilendlocalfunctionsetCleanArgs(argsTable)localcleanArgs={}forkey,valinpairs(argsTable)doiftype(val)=='string'thenval=val:match('^%s*(.-)%s*$')ifval~=''thencleanArgs[key]=valendelsecleanArgs[key]=valendendreturncleanArgsend-- Merge two tables into a new table. If the are any duplicate keys, the values from the second overwrite the values from the first.localfunctionmergeTables(first,second)localmerged={}forkey,valinpairs(first)domerged[key]=valendforkey,valinpairs(second)domerged[key]=valendreturnmergedendlocalfunctiontoOpenTagString(selfClosedHtmlObject)localclosedTagString=tostring(selfClosedHtmlObject)localopenTagString=mw.ustring.gsub(closedTagString,' />$','>')returnopenTagStringendlocalfunctionnormaliseHexTriplet(hexString)ifnothexStringthenreturnnilendlocalhexComponent=mw.ustring.match(hexString,'^#(%x%x%x)$')ormw.ustring.match(hexString,'^#(%x%x%x%x%x%x)$')ifhexComponentand#hexComponent==6thenreturnmw.ustring.upper(hexString)endifhexComponentand#hexComponent==3thenlocalr=mw.ustring.rep(mw.ustring.sub(hexComponent,1,1),2)localg=mw.ustring.rep(mw.ustring.sub(hexComponent,2,2),2)localb=mw.ustring.rep(mw.ustring.sub(hexComponent,3,3),2)return'#'..mw.ustring.upper(r..g..b)endreturnnilend---------- Conversions ----------localfunctiondecimalToPaddedHex(number)localprefixedHex=hex.to_hex(tonumber(number))-- prefixed with '0x'localpadding=#prefixedHex==3and'0'or''returnmw.ustring.gsub(prefixedHex,'0x',padding)endlocalfunctionhexToDecimal(hexNumber)returntonumber(hexNumber,16)endlocalfunctionRGBtoHexTriplet(R,G,B)return'#'..decimalToPaddedHex(R)..decimalToPaddedHex(G)..decimalToPaddedHex(B)endlocalfunctionhexTripletToRGB(hexTriplet)localR_hex,G_hex,B_hex=string.match(hexTriplet,'(%x%x)(%x%x)(%x%x)')returnhexToDecimal(R_hex),hexToDecimal(G_hex),hexToDecimal(B_hex)endlocalfunctionHSVtoRGB(H,S,V)-- per [[HSL and HSV#Converting_to_RGB]]localC=V*SlocalH_prime=H/60localX=C*(1-math.abs(math.fmod(H_prime,2)-1))localR1,G1,B1ifH_prime<=1thenR1=CG1=XB1=0elseifH_prime<=2thenR1=XG1=CB1=0elseifH_prime<=3thenR1=0G1=CB1=XelseifH_prime<=4thenR1=0G1=XB1=CelseifH_prime<=5thenR1=XG1=0B1=CelseifH_prime<=6thenR1=CG1=0B1=Xendlocalm=V-ClocalR=R1+mlocalG=G1+mlocalB=B1+mlocalR_255=math.floor(R*255)localG_255=math.floor(G*255)localB_255=math.floor(B*255)returnR_255,G_255,B_255endlocalfunctionRGBtoHue(R_255,G_255,B_255)-- per [[HSL and HSV#Hue and chroma]]localR=R_255/255localG=G_255/255localB=B_255/255localM=math.max(R,G,B)localm=math.min(R,G,B)localC=M-mlocalH_primeifC==0thenreturnnullelseifM==RthenH_prime=math.fmod(((G-B)/C+6),6)-- adding six before taking mod ensures positive valueelseifM==GthenH_prime=(B-R)/C+2elseifM==BthenH_prime=(R-G)/C+4endlocalH=60*H_primereturnHendlocalfunctionnameToHexTriplet(name)ifnotnamethenreturnnilendlocalcodename=mw.ustring.gsub(mw.ustring.lower(name),' ','')returnnamedColours[codename]end---------- Choose colours ----------localfunctioncalculateColours(H,S,V,minContrast)localbgColour=RGBtoHexTriplet(HSVtoRGB(H,S,V))localtextColour=colourContrastModule._greatercontrast({bgColour})localcontrast=colourContrastModule._ratio({bgColour,textColour})ifcontrast>=minContrastthenreturnbgColour,textColourelseiftextColour=='#FFFFFF'then-- make the background darker and slightly increase the saturationreturncalculateColours(H,math.min(1,S+0.005),math.max(0,V-0.03),minContrast)else-- make the background lighter and slightly decrease the saturationreturncalculateColours(H,math.max(0,S-0.005),math.min(1,V+0.03),minContrast)endendlocalfunctionmakeColours(hue,modeName)localmode=modes[modeName]localisGrey=not(hue)ifisGreythenhue=0endlocalborderSat=isGreyandmodes.grey.sator0.15localborder=RGBtoHexTriplet(HSVtoRGB(hue,borderSat,0.75))localtitleSat=isGreyandmodes.grey.satormode.satlocaltitleBackground,titleForeground=calculateColours(hue,titleSat,mode.val,min_contrast_ratio_large_text)localcontentSat=isGreyandmodes.grey.satormodes.content.satlocalcontentBackground,contentForeground=calculateColours(hue,contentSat,modes.content.val,min_contrast_ratio_normal_text)returnborder,titleForeground,titleBackground,contentForeground,contentBackgroundendlocalfunctionfindHue(colour)localcolourAsNumber=tonumber(colour)ifcolourAsNumberand(-1<colourAsNumber)and(colourAsNumber<360)thenreturncolourAsNumberendlocalcolourAsHexTriplet=normaliseHexTriplet(colour)ornameToHexTriplet(colour)ifcolourAsHexTripletthenreturnRGBtoHue(hexTripletToRGB(colourAsHexTriplet))endreturnnullendlocalfunctionnormaliseMode(mode)ifnotmodeornotmodes[mw.ustring.lower(mode)]ormw.ustring.lower(mode)=='grey'thenreturn'normal'endreturnmw.ustring.lower(mode)end---------- Build output ----------localfunctionboxHeaderOuter(args)localbaseStyle={clear='both',['box-sizing']='border-box',border=(getParam(args,'border-type')or'solid')..' '..(getParam(args,'titleborder')orgetParam(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',}localtag=mw.html.create('div',{selfClosing=true}):addClass('box-header-title-container'):addClass('flex-columns-noflex'):css(baseStyle):css('border-width',(getParam(args,'border-top')orgetParam(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')returntoOpenTagString(tag)endlocalfunctionboxHeaderTopLinks(args)localstyle={float='right',['margin-bottom']='.1em',['font-size']=getParam(args,'font-size')or'80%',color=getParam(args,'titleforeground')or'#000'}localtag=mw.html.create('div',{selfClosing=true}):addClass('plainlinks noprint'):css(style)returntoOpenTagString(tag)endlocalfunctionboxHeaderEditLink(args)localpage=getParam(args,'editpage')ifnotpageorpage=='{{{2}}}'thenreturn''endlocalstyle={color=getParam(args,'titleforeground')or'#000'}localtag=mw.html.create('span'):css(style):wikitext('edit')locallinktext=tostring(tag)locallinktarget=tostring(mw.uri.fullUrl(page,{action='edit',section=getParam(args,'section')}))return'['..linktarget..' '..linktext..'] 'endlocalfunctionboxHeaderViewLink(args)localstyle={color=getParam(args,'titleforeground')or'#000'}localtag=mw.html.create('span'):css(style):wikitext('view')locallinktext=tostring(tag)locallinktarget=':'..getParam(args,'viewpage')return"<b>·</b> [["..linktarget..'|'..linktext..']] 'endlocalfunctionboxHeaderTitle(args)localbaseStyle={['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';}localtagName=getParam(args,'SPAN')and'span'or'h2'localtag=mw.html.create(tagName):css(baseStyle):css('padding-bottom','.1em'):wikitext(getParam(args,'title'))ifgetParam(args,'extra')thenlocalrules=mw.text.split(getParam(args,'extra'),';',true)for_,ruleinpairs(rules)dolocalparts=mw.text.split(rule,':',true)localprop=parts[1]localval=parts[2]ifpropandvalthentag:css(prop,val)endendendreturntostring(tag)endlocalfunctionboxBody(args)localbaseStyle={['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',}localtag=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')returntoOpenTagString(tag)endlocalfunctioncontrastCategories(args)localcats=''localtitleText=nameToHexTriplet(getParam(args,'titleforeground'))ornormaliseHexTriplet(getParam(args,'titleforeground'))or'#000000'localtitleBackground=nameToHexTriplet(getParam(args,'titlebackground'))ornormaliseHexTriplet(getParam(args,'titlebackground'))or'#bcbcbc'localtitleContrast=colourContrastModule._ratio({titleBackground,titleText})localinsufficientTitleContrast=type(titleContrast)=='number'and(titleContrast<min_contrast_ratio_large_text)localbodyText=nameToHexTriplet(getParam(args,'foreground'))ornormaliseHexTriplet(getParam(args,'foreground'))or'#000000'localbodyBackground=nameToHexTriplet(getParam(args,'background'))ornormaliseHexTriplet(getParam(args,'background'))or'#fefeef'localbodyContrast=colourContrastModule._ratio({bodyBackground,bodyText})localinsufficientBodyContrast=type(bodyContrast)=='number'and(bodyContrast<min_contrast_ratio_normal_text)ifinsufficientTitleContrastandinsufficientBodyContrastthenreturn'[[Category:Box-header with insufficient title contrast]][[Category:Box-header with insufficient body contrast]]'elseifinsufficientTitleContrastthenreturn'[[Category:Box-header with insufficient title contrast]]'elseifinsufficientBodyContrastthenreturn'[[Category:Box-header with insufficient body contrast]]'elsereturn''endend---------- Main functions / entry points ------------ Entry point for templates (manually-specified colours)functionp.boxHeader(frame)localargs=getArgs(frame)localpage=args.editpageifnotargs.editpageorargs.editpage==''thenpage=mw.title.getCurrentTitle().prefixedTextendlocaloutput=p._boxHeader(args,page)ifmw.ustring.find(output,'{')thenreturnframe:preprocess(output)endreturnoutputend-- Entry point for modules (manually-specified colours)functionp._boxHeader(_args,page)localargs=setCleanArgs(_args)ifpageandnotargs.editpagethenargs.editpage=pageendifnotargs.titlethenargs.title='{{{title}}}'endlocaloutput={}table.insert(output,boxHeaderOuter(args))ifnotgetParam(args,'EDITLINK')thentable.insert(output,boxHeaderTopLinks(args))ifnotgetParam(args,'noedit')thentable.insert(output,boxHeaderEditLink(args))endifgetParam(args,'viewpage')thentable.insert(output,boxHeaderViewLink(args))endifgetParam(args,'top')thentable.insert(output,getParam(args,'top')..' ')endtable.insert(output,'</div>')endtable.insert(output,boxHeaderTitle(args))table.insert(output,'</div>')table.insert(output,boxBody(args))ifnotgetParam(args,'TOC')thentable.insert(output,'__NOTOC__')endifnotgetParam(args,'EDIT')thentable.insert(output,'__NOEDITSECTION__')endtable.insert(output,contrastCategories(args))returntable.concat(output)end-- Entry point for templates (automatically calculated colours)functionp.autoColour(frame)localargs=getArgs(frame)localcolourParam=getParam(args,'colour')localgeneratedColour=nilifnotcolourParamorcolourParam==''then-- convert the root page name into a number and use thatlocalroot=mw.title.getCurrentTitle().rootPageTitle.prefixedTextlocalrootStart=mw.ustring.sub(root,1,12)localdigitsFromRootStart=mw.ustring.gsub(rootStart,".",function(s)returnmath.fmod(string.byte(s,2)orstring.byte(s,1),10)end)localnumberFromRoot=tonumber(digitsFromRootStart,10)generatedColour=math.fmod(numberFromRoot,360)endlocaloutput=p._autoColour(args,generatedColour)ifmw.ustring.find(output,'{')thenreturnframe:preprocess(output)endreturnoutputend-- Entry point for modules (automatically calculated colours)functionp._autoColour(_args,generatedColour)localargs=setCleanArgs(_args)localhue=generatedColourorfindHue(getParam(args,'colour'))localmode=normaliseMode(getParam(args,'mode'))localborder,titleForeground,titleBackground,contentForeground,contentBackground=makeColours(hue,mode)localboxTemplateArgs=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})returnp._boxHeader(boxTemplateArgs)endreturnp