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
|
|
|
|
}
|