import { createTextStyle } from './text-style' ; import { formatSitesFixesConfig } from './utils/format' ; import { applyColorMatrix , createFilterMatrix } from './utils/matrix' ; import { parseSitesFixesConfig , getSitesFixesFor } from './utils/parse' ; import type { SitePropsIndex } from './utils/parse' ; import { parseArray , formatArray } from '../utils/text' ; import { compareURLPatterns , isURLInList } from '../utils/url' ; import type { FilterConfig , StaticTheme } from '../definitions' ; interface ThemeColors { [ prop ] : number [ ] ; neutralBg : number [ ] ; neutralText : number [ ] ; redBg : number [ ] ; redText : number [ ] ; greenBg : number [ ] ; greenText : number [ ] ; blueBg : number [ ] ; blueText : number [ ] ; fadeBg : number [ ] ; fadeText : number [ ] ; } const darkTheme = { neutralBg : [ 16 , 20 , 23 ] , neutralText : [ 167 , 158 , 139 ] , redBg : [ 64 , 12 , 32 ] , redText : [ 247 , 142 , 102 ] , greenBg : [ 32 , 64 , 48 ] , greenText : [ 128 , 204 , 148 ] , blueBg : [ 32 , 48 , 64 ] , blueText : [ 128 , 182 , 204 ] , fadeBg : [ 16 , 20 , 23 , 0.5 ] , fadeText : [ 167 , 158 , 139 , 0.5 ] , } ; const lightTheme = { neutralBg : [ 255 , 242 , 228 ] , neutralText : [ 0 , 0 , 0 ] , redBg : [ 255 , 85 , 170 ] , redText : [ 140 , 14 , 48 ] , greenBg : [ 192 , 255 , 170 ] , greenText : [ 0 , 128 , 0 ] , blueBg : [ 173 , 215 , 229 ] , blueText : [ 28 , 16 , 171 ] , fadeBg : [ 0 , 0 , 0 , 0.5 ] , fadeText : [ 0 , 0 , 0 , 0.5 ] , } ; function rgb ( [ r , g , b , a ] ) { if ( typeof a === 'number' ) { return `rgba(${ r }, ${ g }, ${ b }, ${ a })` ; } return `rgb(${ r }, ${ g }, ${ b })` ; } function mix ( color1 , color2 , t ) { return color1 . map ( ( c , i ) => Math . round ( c * ( 1 - t ) + color2 [ i ] * t ) ) ; } export default function createStaticStylesheet ( config , url , frameURL , staticThemes , staticThemesIndex ) { const srcTheme = config . mode === 1 ? darkTheme : lightTheme ; const theme = Object . entries ( srcTheme ) . reduce ( ( t , [ prop , color ] ) => { const [ r , g , b , a ] = color ; t [ prop ] = applyColorMatrix ( [ r , g , b ] , createFilterMatrix ( { ... config , mode : 0 } ) ) ; if ( a !== undefined ) { t [ prop ] . push ( a ) ; } return t ; } , { } as ThemeColors ) ; const commonTheme = getCommonTheme ( staticThemes , staticThemesIndex ) ; const siteTheme = getThemeFor ( frameURL || url , staticThemes , staticThemesIndex ) ; const lines = [ ] ; if ( ! siteTheme || ! siteTheme . noCommon ) { lines . push ( '/* Common theme */' ) ; lines . push ( ... ruleGenerators . map ( ( gen ) => gen ( commonTheme , theme ) ) ) ; } if ( siteTheme ) { lines . push ( `/* Theme for ${ siteTheme . url . join ( ' ' ) } */` ) ; lines . push ( ... ruleGenerators . map ( ( gen ) => gen ( siteTheme , theme ) ) ) ; } if ( config . useFont || config . textStroke > 0 ) { lines . push ( '/* Font */' ) ; lines . push ( createTextStyle ( config ) ) ; } return lines . filter ( ( ln ) => ln ) . join ( '\n' ) ; } function createRuleGen ( getSelectors , generateDeclarations , modifySelector = ( s ) => s ) { return ( siteTheme , themeColors ) => { const selectors = getSelectors ( siteTheme ) ; if ( selectors == null || selectors . length === 0 ) { return null ; } const lines = [ ] ; selectors . forEach ( ( s , i ) => { let ln = modifySelector ( s ) ; if ( i < selectors . length - 1 ) { ln += ',' ; } else { ln += ' {' ; } lines . push ( ln ) ; } ) ; const declarations = generateDeclarations ( themeColors ) ; declarations . forEach ( ( d ) => lines . push ( `    ${ d } !important;` ) ) ; lines . push ( '}' ) ; return lines . join ( '\n' ) ; } ; } const mx = { bg : { hover : 0.075 , active : 0.1 , } , fg : { hover : 0.25 , active : 0.5 , } , border : 0.5 , } ; const ruleGenerators = [ createRuleGen ( ( t ) => t . neutralBg , ( t ) => [ `background-color: ${ rgb ( t . neutralBg ) }` ] ) , createRuleGen ( ( t ) => t . neutralBgActive , ( t ) => [ `background-color: ${ rgb ( t . neutralBg ) }` ] ) , createRuleGen ( ( t ) => t . neutralBgActive , ( t ) => [ `background-color: ${ rgb ( mix ( t . neutralBg , [ 255 , 255 , 255 ] , mx . bg . hover ) ) }` ] , ( s ) => `${ s }:hover` ) , createRuleGen ( ( t ) => t . neutralBgActive , ( t ) => [ `background-color: ${ rgb ( mix ( t . neutralBg , [ 255 , 255 , 255 ] , mx . bg . active ) ) }` ] , ( s ) => `${ s }:active, ${ s }:focus` ) , createRuleGen ( ( t ) => t . neutralText , ( t ) => [ `color: ${ rgb ( t . neutralText ) }` ] ) , createRuleGen ( ( t ) => t . neutralTextActive , ( t ) => [ `color: ${ rgb ( t . neutralText ) }` ] ) , createRuleGen ( ( t ) => t . neutralTextActive , ( t ) => [ `color: ${ rgb ( mix ( t . neutralText , [ 255 , 255 , 255 ] , mx . fg . hover ) ) }` ] , ( s ) => `${ s }:hover` ) , createRuleGen ( ( t ) => t . neutralTextActive , ( t ) => [ `color: ${ rgb ( mix ( t . neutralText , [ 255 , 255 , 255 ] , mx . fg . active ) ) }` ] , ( s ) => `${ s }:active, ${ s }:focus` ) , createRuleGen ( ( t ) => t . neutralBorder , ( t ) => [ `border-color: ${ rgb ( mix ( t . neutralBg , t . neutralText , mx . border ) ) }` ] ) , createRuleGen ( ( t ) => t . redBg , ( t ) => [ `background-color: ${ rgb ( t . redBg ) }` ] ) , createRuleGen ( ( t ) => t . redBgActive , ( t ) => [ `background-color: ${ rgb ( t . redBg ) }` ] ) , createRuleGen ( ( t ) => t . redBgActive , ( t ) => [ `background-color: ${ rgb ( mix ( t . redBg , [ 255 , 0 , 64 ] , mx . bg . hover ) ) }` ] , ( s ) => `${ s }:hover` ) , createRuleGen ( ( t ) => t . redBgActive , ( t ) => [ `background-color: ${ rgb ( mix ( t . redBg , [ 255 , 0 , 64 ] , mx . bg . active ) ) }` ] , ( s ) => `${ s }:active, ${ s }:focus` ) , createRuleGen ( ( t ) => t . redText , ( t ) => [ `color: ${ rgb ( t . redText ) }` ] ) , createRuleGen ( ( t ) => t . redTextActive , ( t ) => [ `color: ${ rgb ( t . redText ) }` ] ) , createRuleGen ( ( t ) => t . redTextActive , ( t ) => [ `color: ${ rgb ( mix ( t . redText , [ 255 , 255 , 0 ] , mx . fg . hover ) ) }` ] , ( s ) => `${ s }:hover` ) , createRuleGen ( ( t ) => t . redTextActive , ( t ) => [ `color: ${ rgb ( mix ( t . redText , [ 255 , 255 , 0 ] , mx . fg . active ) ) }` ] , ( s ) => `${ s }:active, ${ s }:focus` ) , createRuleGen ( ( t ) => t . redBorder , ( t ) => [ `border-color: ${ rgb ( mix ( t . redBg , t . redText , mx . border ) ) }` ] ) , createRuleGen ( ( t ) => t . greenBg , ( t ) => [ `background-color: ${ rgb ( t . greenBg ) }` ] ) , createRuleGen ( ( t ) => t . greenBgActive , ( t ) => [ `background-color: ${ rgb ( t . greenBg ) }` ] ) , createRuleGen ( ( t ) => t . greenBgActive , ( t ) => [ `background-color: ${ rgb ( mix ( t . greenBg , [ 128 , 255 , 182 ] , mx . bg . hover ) ) }` ] , ( s ) => `${ s }:hover` ) , createRuleGen ( ( t ) => t . greenBgActive , ( t ) => [ `background-color: ${ rgb ( mix ( t . greenBg , [ 128 , 255 , 182 ] , mx . bg . active ) ) }` ] , ( s ) => `${ s }:active, ${ s }:focus` ) , createRuleGen ( ( t ) => t . greenText , ( t ) => [ `color: ${ rgb ( t . greenText ) }` ] ) , createRuleGen ( ( t ) => t . greenTextActive , ( t ) => [ `color: ${ rgb ( t . greenText ) }` ] ) , createRuleGen ( ( t ) => t . greenTextActive , ( t ) => [ `color: ${ rgb ( mix ( t . greenText , [ 182 , 255 , 224 ] , mx . fg . hover ) ) }` ] , ( s ) => `${ s }:hover` ) , createRuleGen ( ( t ) => t . greenTextActive , ( t ) => [ `color: ${ rgb ( mix ( t . greenText , [ 182 , 255 , 224 ] , mx . fg . active ) ) }` ] , ( s ) => `${ s }:active, ${ s }:focus` ) , createRuleGen ( ( t ) => t . greenBorder , ( t ) => [ `border-color: ${ rgb ( mix ( t . greenBg , t . greenText , mx . border ) ) }` ] ) , createRuleGen ( ( t ) => t . blueBg , ( t ) => [ `background-color: ${ rgb ( t . blueBg ) }` ] ) , createRuleGen ( ( t ) => t . blueBgActive , ( t ) => [ `background-color: ${ rgb ( t . blueBg ) }` ] ) , createRuleGen ( ( t ) => t . blueBgActive , ( t ) => [ `background-color: ${ rgb ( mix ( t . blueBg , [ 0 , 128 , 255 ] , mx . bg . hover ) ) }` ] , ( s ) => `${ s }:hover` ) , createRuleGen ( ( t ) => t . blueBgActive , ( t ) => [ `background-color: ${ rgb ( mix ( t . blueBg , [ 0 , 128 , 255 ] , mx . bg . active ) ) }` ] , ( s ) => `${ s }:active, ${ s }:focus` ) , createRuleGen ( ( t ) => t . blueText , ( t ) => [ `color: ${ rgb ( t . blueText ) }` ] ) , createRuleGen ( ( t ) => t . blueTextActive , ( t ) => [ `color: ${ rgb ( t . blueText ) }` ] ) , createRuleGen ( ( t ) => t . blueTextActive , ( t ) => [ `color: ${ rgb ( mix ( t . blueText , [ 182 , 224 , 255 ] , mx . fg . hover ) ) }` ] , ( s ) => `${ s }:hover` ) , createRuleGen ( ( t ) => t . blueTextActive , ( t ) => [ `color: ${ rgb ( mix ( t . blueText , [ 182 , 224 , 255 ] , mx . fg . active ) ) }` ] , ( s ) => `${ s }:active, ${ s }:focus` ) , createRuleGen ( ( t ) => t . blueBorder , ( t ) => [ `border-color: ${ rgb ( mix ( t . blueBg , t . blueText , mx . border ) ) }` ] ) , createRuleGen ( ( t ) => t . fadeBg , ( t ) => [ `background-color: ${ rgb ( t . fadeBg ) }` ] ) , createRuleGen ( ( t ) => t . fadeText , ( t ) => [ `color: ${ rgb ( t . fadeText ) }` ] ) , createRuleGen ( ( t ) => t . transparentBg , ( ) => [ 'background-color: transparent' ] ) , createRuleGen ( ( t ) => t . noImage , ( ) => [ 'background-image: none' ] ) , createRuleGen ( ( t ) => t . invert , ( ) => [ 'filter: invert(100%) hue-rotate(180deg)' ] ) , ] ; const staticThemeCommands = { 'NO COMMON' : 'noCommon' , 'NEUTRAL BG' : 'neutralBg' , 'NEUTRAL BG ACTIVE' : 'neutralBgActive' , 'NEUTRAL TEXT' : 'neutralText' , 'NEUTRAL TEXT ACTIVE' : 'neutralTextActive' , 'NEUTRAL BORDER' : 'neutralBorder' , 'RED BG' : 'redBg' , 'RED BG ACTIVE' : 'redBgActive' , 'RED TEXT' : 'redText' , 'RED TEXT ACTIVE' : 'redTextActive' , 'RED BORDER' : 'redBorder' , 'GREEN BG' : 'greenBg' , 'GREEN BG ACTIVE' : 'greenBgActive' , 'GREEN TEXT' : 'greenText' , 'GREEN TEXT ACTIVE' : 'greenTextActive' , 'GREEN BORDER' : 'greenBorder' , 'BLUE BG' : 'blueBg' , 'BLUE BG ACTIVE' : 'blueBgActive' , 'BLUE TEXT' : 'blueText' , 'BLUE TEXT ACTIVE' : 'blueTextActive' , 'BLUE BORDER' : 'blueBorder' , 'FADE BG' : 'fadeBg' , 'FADE TEXT' : 'fadeText' , 'TRANSPARENT BG' : 'transparentBg' , 'NO IMAGE' : 'noImage' , 'INVERT' : 'invert' , } ; export function parseStaticThemes ( $themes ) { return parseSitesFixesConfig < StaticTheme > ( $themes , { commands : Object . keys ( staticThemeCommands ) , getCommandPropName : ( command ) => staticThemeCommands [ command ] , parseCommandValue : ( command , value ) => { if ( command === 'NO COMMON' ) { return true ; } return parseArray ( value ) ; } } ) ; } function camelCaseToUpperCase ( text ) { return text . replace ( /([a-z])([A-Z])/g , '$1 $2' ) . toUpperCase ( ) ; } export function formatStaticThemes ( staticThemes ) { const themes = staticThemes . slice ( ) . sort ( ( a , b ) => compareURLPatterns ( a . url [ 0 ] , b . url [ 0 ] ) ) ; return formatSitesFixesConfig ( themes , { props : Object . values ( staticThemeCommands ) , getPropCommandName : camelCaseToUpperCase , formatPropValue : ( prop , value ) => { if ( prop === 'noCommon' ) { return '' ; } return formatArray ( value as string [ ] ) . trim ( ) ; } , shouldIgnoreProp : ( prop , value ) => { if ( prop === 'noCommon' ) { return ! value ; } return ! ( Array . isArray ( value ) && value . length > 0 ) ; } } ) ; } function getCommonTheme ( staticThemes , staticThemesIndex ) { const length = parseInt ( staticThemesIndex . offsets . substring ( 4 , 4 + 3 ) , 36 ) ; const staticThemeText = staticThemes . substring ( 0 , length ) ; return parseStaticThemes ( staticThemeText ) [ 0 ] ; } function getThemeFor ( url , staticThemes , staticThemesIndex ) { const themes = getSitesFixesFor < StaticTheme > ( url , staticThemes , staticThemesIndex , { commands : Object . keys ( staticThemeCommands ) , getCommandPropName : ( command ) => staticThemeCommands [ command ] , parseCommandValue : ( command , value ) => { if ( command === 'NO COMMON' ) { return true ; } return parseArray ( value ) ; } } ) ; const sortedBySpecificity = themes . slice ( 1 ) . map ( ( theme ) => { return { specificity : isURLInList ( url , theme . url ) ? theme . url [ 0 ] . length : 0 , theme } ; } ) . filter ( ( { specificity } ) => specificity > 0 ) . sort ( ( a , b ) => b . specificity - a . specificity ) ; if ( sortedBySpecificity . length === 0 ) { return null ; } return sortedBySpecificity [ 0 ] . theme ; }