Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
260 lines
6.8 KiB
Go
260 lines
6.8 KiB
Go
package sass
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/bep/godartsass/v2"
|
|
"github.com/cortezaproject/corteza/server/assets"
|
|
"github.com/cortezaproject/corteza/server/pkg/logger"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
GeneralTheme = "general"
|
|
LightTheme = "light"
|
|
DarkTheme = "dark"
|
|
SectionRoot = "root"
|
|
SectionMain = "main"
|
|
SectionTheme = "theme"
|
|
)
|
|
|
|
var (
|
|
StylesheetCache = newStylesheetCache()
|
|
sassVariablesPattern = regexp.MustCompile(`(\$[a-zA-Z_-]+):\s*([^;]+);`)
|
|
)
|
|
|
|
// DefaultCSS contains css contents from minified-custom.css and custom-css editor content
|
|
func DefaultCSS(log *zap.Logger, customCSS string) string {
|
|
var strBuilder strings.Builder
|
|
|
|
//read css contents from minified-custom.css
|
|
minifiedCSSFile, err := assets.Files(logger.Default(), "").Open(path.Join("css", "minified-custom.css"))
|
|
if err != nil {
|
|
log.Error("failed to open minified-custom.css file", zap.Error(err))
|
|
}
|
|
|
|
reader := bufio.NewReader(minifiedCSSFile)
|
|
_, err = io.Copy(&strBuilder, reader)
|
|
if err != nil {
|
|
log.Error("failed to read css content from minified-custom.css", zap.Error(err))
|
|
}
|
|
|
|
if customCSS != "" {
|
|
strBuilder.WriteString(customCSS)
|
|
}
|
|
|
|
processedCSS := strBuilder.String()
|
|
|
|
StylesheetCache.Set("default-theme", processedCSS)
|
|
|
|
return processedCSS
|
|
}
|
|
|
|
func Transpile(transpiler *godartsass.Transpiler, log *zap.Logger, themeID, themeSASS, customCSS, sassDirPath string) (err error) {
|
|
// process root section
|
|
err = processSass(transpiler, log, SectionRoot, themeID, themeSASS, "", sassDirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// process main section
|
|
if themeID == LightTheme {
|
|
err = processSass(transpiler, log, SectionMain, themeID, themeSASS, customCSS, sassDirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
//process theme section
|
|
err = processSass(transpiler, log, SectionTheme, themeID, themeSASS, customCSS, sassDirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func processSass(transpiler *godartsass.Transpiler, log *zap.Logger, section, themeID, themeSASS, customCSS, sassDirPath string) (err error) {
|
|
var (
|
|
stringsBuilder strings.Builder
|
|
isCustomCssSyntaxValid bool
|
|
)
|
|
|
|
// add theme-mode variable to the top of the section
|
|
stringsBuilder.WriteString(fmt.Sprintf("$theme-mode: %s;\n", themeID))
|
|
|
|
if themeSASS != "" {
|
|
err = jsonToSass(themeSASS, &stringsBuilder)
|
|
if err != nil {
|
|
log.Error("failed to unmarshal branding sass variables", zap.Error(err))
|
|
return err
|
|
}
|
|
}
|
|
|
|
if customCSS != "" {
|
|
_, err = transpileSass(transpiler, customCSS)
|
|
if err != nil {
|
|
log.Error("sass compilation for custom css failed", zap.Error(err))
|
|
} else {
|
|
isCustomCssSyntaxValid = true
|
|
// Get SASS variables from the custom CSS editor and give them precedence over branding variables
|
|
customVariables := sassVariablesPattern.FindAllString(customCSS, -1)
|
|
for _, customVariable := range customVariables {
|
|
stringsBuilder.WriteString(fmt.Sprintf("%s \n", customVariable))
|
|
}
|
|
}
|
|
}
|
|
|
|
//Save branding sass variables to cache
|
|
sassVariables, err := readSassFiles(log, "scss/variables")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stringsBuilder.WriteString(sassVariables)
|
|
StylesheetCache.Set("sass", stringsBuilder.String())
|
|
|
|
// start processing a section
|
|
sassSection, err := readSassFiles(log, path.Join("scss", section))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stringsBuilder.WriteString(StylesheetCache.Get("sass"))
|
|
stringsBuilder.WriteString(sassSection)
|
|
|
|
// when a user provides sets WEBAPP_SCSS_DIR_PATH environment variable
|
|
if sassDirPath != "" {
|
|
customSass, err := readSassFiles(log, sassDirPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stringsBuilder.WriteString(customSass)
|
|
}
|
|
|
|
if customCSS != "" && isCustomCssSyntaxValid {
|
|
//Custom CSS editor selector block
|
|
selectorBlock := sassVariablesPattern.ReplaceAllString(customCSS, "")
|
|
stringsBuilder.WriteString(selectorBlock)
|
|
}
|
|
|
|
transpiledCss, err := transpileSass(transpiler, stringsBuilder.String())
|
|
if err != nil {
|
|
log.Error("sass compilation failure", zap.Error(err))
|
|
}
|
|
|
|
// in case of sass error in custom css sass compilation,
|
|
// use compiled css from branding sass and append custom css value to it
|
|
if !isCustomCssSyntaxValid {
|
|
stringsBuilder.Reset()
|
|
stringsBuilder.WriteString(transpiledCss)
|
|
stringsBuilder.WriteString(customCSS)
|
|
|
|
// append un-compiled custom css content to transpiled css
|
|
transpiledCss = stringsBuilder.String()
|
|
}
|
|
|
|
//save the transpiled css to stylesheet cache
|
|
sectionKey := fmt.Sprintf("%s-%s", section, themeID)
|
|
StylesheetCache.Set(sectionKey, transpiledCss)
|
|
|
|
return nil
|
|
}
|
|
|
|
// transpileSass computes sass to css by the transpiler
|
|
func transpileSass(transpiler *godartsass.Transpiler, sass string) (string, error) {
|
|
args := godartsass.Args{
|
|
Source: sass,
|
|
}
|
|
execute, err := transpiler.Execute(args)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return execute.CSS, nil
|
|
}
|
|
|
|
func DartSassTranspiler(log *zap.Logger) *godartsass.Transpiler {
|
|
transpiler, err := godartsass.Start(godartsass.Options{
|
|
DartSassEmbeddedFilename: "sass",
|
|
})
|
|
|
|
if err != nil {
|
|
log.Warn("dart sass is not installed in your system", zap.Error(err))
|
|
return nil
|
|
}
|
|
|
|
return transpiler
|
|
}
|
|
|
|
// readSassFiles reads SASS contents from provided embedded directory and subdirectories then converts them to a string
|
|
func readSassFiles(log *zap.Logger, dirPath string) (string, error) {
|
|
var stringsBuilder strings.Builder
|
|
|
|
filenames, subDirs, err := assets.DirEntries(dirPath)
|
|
if err != nil {
|
|
log.Error(fmt.Sprintf("failed to read assets/src/%s entries", dirPath), zap.Error(err))
|
|
return "", err
|
|
}
|
|
|
|
if len(filenames) > 0 {
|
|
err := readSassContents(log, dirPath, filenames, &stringsBuilder)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
if len(subDirs) > 0 {
|
|
for _, subDir := range subDirs {
|
|
sassContents, err := readSassFiles(log, path.Join(dirPath, subDir))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
stringsBuilder.WriteString(sassContents)
|
|
}
|
|
}
|
|
|
|
return stringsBuilder.String(), nil
|
|
}
|
|
|
|
func readSassContents(log *zap.Logger, dirPath string, filenames []string, stringsBuilder *strings.Builder) error {
|
|
assetFiles := assets.Files(logger.Default(), "")
|
|
|
|
for _, fileName := range filenames {
|
|
open, err := assetFiles.Open(path.Join(dirPath, fileName))
|
|
if err != nil {
|
|
log.Error(fmt.Sprintf("failed to open asset %s file", fileName), zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
reader := bufio.NewReader(open)
|
|
_, err = io.Copy(stringsBuilder, reader)
|
|
if err != nil {
|
|
log.Error(fmt.Sprintf("failed to copy sass content from %s", fileName), zap.Error(err))
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// jsonToSass converts JSON string to SASS variable assignment string
|
|
func jsonToSass(jsonStr string, strBuilder *strings.Builder) error {
|
|
var colorMap map[string]string
|
|
|
|
err := json.Unmarshal([]byte(jsonStr), &colorMap)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for key, value := range colorMap {
|
|
strBuilder.WriteString(fmt.Sprintf("$%s: %s;\n", key, value))
|
|
}
|
|
|
|
return nil
|
|
}
|