alice-lg/pkg/http/theme.go

175 lines
4.0 KiB
Go
Raw Normal View History

2021-10-22 22:40:03 +02:00
package http
2018-06-20 19:47:27 +02:00
/*
The theme provides a method for adding customized CSS
or Javascript to Alice:
A theme directory can be specified in the config.
Stylesheets and Javascript residing in the theme root
directory will be included in the frontends HTML.
Additional files can be added in subdirectories.
These are served aswell and can be used for additional
assets. (E.g. a logo)
*/
import (
"fmt"
"log"
"os"
2018-06-25 23:46:26 +02:00
"strconv"
2018-06-20 19:47:27 +02:00
"strings"
"io/ioutil"
"net/http"
2018-06-25 23:46:26 +02:00
"path/filepath"
"github.com/julienschmidt/httprouter"
2021-10-27 17:54:51 +00:00
"github.com/alice-lg/alice-lg/pkg/config"
2018-06-20 19:47:27 +02:00
)
2021-03-22 15:10:00 +01:00
// Theme is a client customization through additional
// HTML, CSS and JS content.
2018-06-20 19:47:27 +02:00
type Theme struct {
2021-10-27 17:54:51 +00:00
Config config.ThemeConfig
2018-06-20 19:47:27 +02:00
}
2021-03-22 15:10:00 +01:00
// NewTheme creates a theme from a config
2021-10-27 17:54:51 +00:00
func NewTheme(config config.ThemeConfig) *Theme {
return &Theme{
2018-06-20 19:47:27 +02:00
Config: config,
}
}
2021-03-22 15:10:00 +01:00
// Get includable files from theme directory
func (t *Theme) listIncludes(suffix string) []string {
2018-06-20 19:47:27 +02:00
includes := []string{}
2021-03-22 15:10:00 +01:00
files, err := ioutil.ReadDir(t.Config.Path)
2018-06-20 19:47:27 +02:00
if err != nil {
return []string{}
}
for _, file := range files {
if file.IsDir() {
continue
}
2018-06-25 23:46:26 +02:00
filename := file.Name()
if strings.HasPrefix(filename, ".") {
continue
}
if strings.HasSuffix(filename, suffix) {
includes = append(includes, filename)
2018-06-20 19:47:27 +02:00
}
}
return includes
}
2021-03-22 15:10:00 +01:00
// HashInclude calculates a hashvalue for an include file,
// to help with cache invalidation, when the file changes.
//
// We are using the timestamp of the last access as Unix()
// encoded as hex
func (t *Theme) HashInclude(include string) string {
path := filepath.Join(t.Config.Path, include)
2018-06-25 23:46:26 +02:00
stat, err := os.Stat(path)
if err != nil {
return ""
}
modTime := stat.ModTime().UTC()
timestamp := modTime.Unix()
return strconv.FormatInt(timestamp, 16)
}
2021-03-22 15:10:00 +01:00
// Stylesheets retrieve a list of includeable stylesheets, with
// their md5sum as hash
func (t *Theme) Stylesheets() []string {
return t.listIncludes(".css")
2018-06-20 19:47:27 +02:00
}
2021-03-22 15:10:00 +01:00
// StylesheetIncludes make include statements for stylesheet
func (t *Theme) StylesheetIncludes() string {
2018-06-25 23:46:26 +02:00
includes := []string{}
2021-03-22 15:10:00 +01:00
for _, stylesheet := range t.Stylesheets() {
hash := t.HashInclude(stylesheet)
2018-06-25 23:46:26 +02:00
include := fmt.Sprintf(
"<link rel=\"stylesheet\" href=\"%s/%s?%s\" />",
2021-03-22 15:10:00 +01:00
t.Config.BasePath, stylesheet, hash,
2018-06-25 23:46:26 +02:00
)
includes = append(includes, include)
}
return strings.Join(includes, "\n")
}
2021-03-22 15:10:00 +01:00
// Scripts retrieve a list of includeable javascipts
func (t *Theme) Scripts() []string {
return t.listIncludes(".js")
2018-06-20 19:47:27 +02:00
}
2021-03-22 15:10:00 +01:00
// ScriptIncludes makes include statement for scripts
func (t *Theme) ScriptIncludes() string {
2018-06-25 23:46:26 +02:00
includes := []string{}
2021-03-22 15:10:00 +01:00
for _, script := range t.Scripts() {
hash := t.HashInclude(script)
2018-06-25 23:46:26 +02:00
include := fmt.Sprintf(
2022-07-25 22:51:25 +02:00
"<script type=\"text/javascript\" src=\"%s/%s?%s\" defer></script>",
2021-03-22 15:10:00 +01:00
t.Config.BasePath, script, hash,
2018-06-25 23:46:26 +02:00
)
includes = append(includes, include)
}
return strings.Join(includes, "\n")
}
2021-03-22 15:10:00 +01:00
// Handler is the theme HTTP handler
func (t *Theme) Handler() http.Handler {
2018-06-20 19:47:27 +02:00
// Serve the content using the file server
2021-03-22 15:10:00 +01:00
path := t.Config.Path
2018-06-20 19:47:27 +02:00
themeFilesHandler := http.StripPrefix(
2021-03-22 15:10:00 +01:00
t.Config.BasePath, http.FileServer(http.Dir(path)))
2018-06-20 19:47:27 +02:00
return themeFilesHandler
}
2018-06-25 23:46:26 +02:00
2021-03-22 15:10:00 +01:00
// RegisterThemeAssets registers the theme at path
func (t *Theme) RegisterThemeAssets(router *httprouter.Router) error {
fsPath := t.Config.Path
2018-06-25 23:46:26 +02:00
if fsPath == "" {
return nil // nothing to do here
}
if _, err := os.Stat(fsPath); err != nil {
2021-03-22 15:10:00 +01:00
return fmt.Errorf("theme path '%s' could not be found", fsPath)
2018-06-25 23:46:26 +02:00
}
log.Println("Using theme at:", fsPath)
// We have a theme, install handler
2021-03-22 15:10:00 +01:00
path := fmt.Sprintf("%s/*path", t.Config.BasePath)
router.Handler("GET", path, t.Handler())
2018-06-25 23:46:26 +02:00
return nil
}
2021-03-22 15:10:00 +01:00
// PrepareClientHTML prepares the document and fills placeholders
// with scripts and stylesheet.
func (t *Theme) PrepareClientHTML(html string) string {
stylesheets := t.StylesheetIncludes()
scripts := t.ScriptIncludes()
2018-06-25 23:46:26 +02:00
html = strings.Replace(html,
"<!-- ###THEME_STYLESHEETS### -->",
stylesheets, 1)
html = strings.Replace(html,
"<!-- ###THEME_SCRIPTS### -->",
scripts, 1)
return html
}