aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2023-10-18 12:43:51 -0700
committerMike Crute <mike@crute.us>2023-10-18 12:43:51 -0700
commit86639f865a9a38ced67eb9d80b915436e0819a77 (patch)
tree55ffedc52e3dd9401fbbd179ef4a926fd13683ee
parente8178ad95305a3096cef410626164559693052f6 (diff)
downloadgolib-86639f865a9a38ced67eb9d80b915436e0819a77.tar.bz2
golib-86639f865a9a38ced67eb9d80b915436e0819a77.tar.xz
golib-86639f865a9a38ced67eb9d80b915436e0819a77.zip
echo: add some template functions
-rw-r--r--echo/tplfuncs/embed_csp.go75
-rw-r--r--echo/tplfuncs/humanize.go23
-rw-r--r--echo/tplfuncs/picture_tag.go16
3 files changed, 114 insertions, 0 deletions
diff --git a/echo/tplfuncs/embed_csp.go b/echo/tplfuncs/embed_csp.go
new file mode 100644
index 0000000..44930c4
--- /dev/null
+++ b/echo/tplfuncs/embed_csp.go
@@ -0,0 +1,75 @@
1package tplfuncs
2
3import (
4 "fmt"
5 "html/template"
6 "io"
7 "io/fs"
8 "path"
9
10 "code.crute.us/mcrute/golib/echo/middleware"
11
12 "github.com/labstack/echo/v4"
13)
14
15type TemplateEmbeder struct {
16 templateStore fs.FS
17}
18
19func (t *TemplateEmbeder) ConfigureTemplateStore(store fs.FS) {
20 t.templateStore = store
21}
22
23func (t *TemplateEmbeder) Embed(filename string) ([]byte, error) {
24 if t.templateStore == nil {
25 return nil, fmt.Errorf("EmbedWithCSP: has not been setup with template store")
26 }
27
28 fd, err := t.templateStore.Open(filename)
29 if err != nil {
30 return nil, err
31 }
32 defer fd.Close()
33
34 fc, err := io.ReadAll(fd)
35 if err != nil {
36 return nil, err
37 }
38
39 return fc, nil
40}
41
42func (t *TemplateEmbeder) EmbedHTML(filename string) (any, error) {
43 d, err := t.Embed(filename)
44 return template.HTML(d), err
45}
46
47func (t *TemplateEmbeder) embedWithCSP(filename string, c echo.Context) ([]byte, error) {
48 fc, err := t.Embed(filename)
49 if err != nil {
50 return nil, err
51 }
52
53 csp := &middleware.ContentSecurityPolicyConfig{}
54 switch path.Ext(filename) {
55 case ".js", ".json":
56 csp.ScriptSrc = []middleware.CSPDirective{
57 middleware.CSPSha256FromBytes(fc),
58 }
59 case ".css":
60 csp.StyleSrc = []middleware.CSPDirective{
61 middleware.CSPSha256FromBytes(fc),
62 }
63 default:
64 return nil, fmt.Errorf("EmbedWithCSP: file %s can not be embedded", filename)
65 }
66
67 middleware.ExtendCSP(c, csp)
68
69 return fc, nil
70}
71
72func (t *TemplateEmbeder) EmbedJSWithCSP(filename string, c echo.Context) (any, error) {
73 d, err := t.embedWithCSP(filename, c)
74 return template.JS(d), err
75}
diff --git a/echo/tplfuncs/humanize.go b/echo/tplfuncs/humanize.go
new file mode 100644
index 0000000..4fc47a3
--- /dev/null
+++ b/echo/tplfuncs/humanize.go
@@ -0,0 +1,23 @@
1package tplfuncs
2
3import (
4 "fmt"
5 "strings"
6)
7
8// JoinEnglish renders a list of strings with commas between them and if
9// there are three or more words will include the word "and" before the
10// final word.
11func JoinEnglish(words []string) string {
12 switch len(words) {
13 case 0:
14 return ""
15 case 1:
16 return words[0]
17 case 2:
18 return fmt.Sprintf("%s and %s", words[0], words[1])
19 default:
20 base := strings.Join(words[:len(words)-1], ", ")
21 return fmt.Sprintf("%s, and %s", base, words[len(words)-1])
22 }
23}
diff --git a/echo/tplfuncs/picture_tag.go b/echo/tplfuncs/picture_tag.go
new file mode 100644
index 0000000..ddec4aa
--- /dev/null
+++ b/echo/tplfuncs/picture_tag.go
@@ -0,0 +1,16 @@
1package tplfuncs
2
3import (
4 "fmt"
5 "html/template"
6)
7
8// RenderPictureTag renders a picture tag with a webp and jpeg source
9// and alt text.
10func RenderPictureTag(img, alt string) template.HTML {
11 return template.HTML(fmt.Sprintf(`<picture>
12 <source srcset="%s.webp" type="image/webp" />
13 <source srcset="%s.jpg" type="image/jpeg" />
14 <img src="%s.jpg" alt="%s" loading="lazy" />
15</picture>`, img, img, img, alt))
16}