footer: Add a semi-flexible template system

This commit is contained in:
Tom Wiesing 2023-01-06 22:52:47 +01:00
parent bda763725e
commit 021fc3cc7e
No known key found for this signature in database
26 changed files with 239 additions and 208 deletions

View file

@ -28,7 +28,7 @@ type componentContext struct {
}
func (admin *Admin) components(r *http.Request) (cp componentContext, err error) {
admin.Dependencies.Custom.Update(&cp)
admin.Dependencies.Custom.Update(&cp, r)
cp.Analytics = *admin.Analytics
return
@ -49,7 +49,7 @@ type ingredientsContext struct {
}
func (admin *Admin) ingredients(r *http.Request) (cp ingredientsContext, err error) {
admin.Dependencies.Custom.Update(&cp)
admin.Dependencies.Custom.Update(&cp, r)
// find the instance itself!
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), mux.Vars(r)["slug"])

View file

@ -87,7 +87,7 @@ type indexContext struct {
}
func (admin *Admin) index(r *http.Request) (idx indexContext, err error) {
admin.Dependencies.Custom.Update(&idx)
admin.Dependencies.Custom.Update(&idx, r)
idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true)
return
}

View file

@ -2,7 +2,6 @@ package admin
import (
_ "embed"
"html/template"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
@ -25,13 +24,12 @@ var instanceTemplate = static.AssetsAdmin.MustParseShared(
type instanceContext struct {
custom.BaseContext
CSRF template.HTML
Instance models.Instance
Info status.WissKI
}
func (admin *Admin) instance(r *http.Request) (is instanceContext, err error) {
admin.Dependencies.Custom.Update(&is)
admin.Dependencies.Custom.Update(&is, r)
is.CSRF = csrf.TemplateField(r)

View file

@ -3,7 +3,6 @@ package admin
import (
"context"
"errors"
"html/template"
"net/http"
_ "embed"
@ -27,12 +26,11 @@ type userContext struct {
custom.BaseContext
httpx.FormContext
CSRF template.HTML
Users []*auth.AuthUser
}
func (admin *Admin) users(r *http.Request) (uc userContext, err error) {
admin.Dependencies.Custom.Update(&uc)
admin.Dependencies.Custom.Update(&uc, r)
uc.CSRF = csrf.TemplateField(r)
uc.Users, err = admin.Dependencies.Auth.Users(r.Context())

View file

@ -1,9 +1,6 @@
{{ template "_base.html" . }}
{{ define "title" }}WissKI Distillery{{ end }}
{{ define "header/time" }}
<!-- no header/time -->
{{ end }}
{{ define "header"}}
<!-- no header -->
{{ end }}

View file

@ -33,7 +33,7 @@ var homeTemplate = static.AssetsHome.MustParseShared("home.html", homeHTMLStr)
func (home *Home) homeRender(ctx context.Context) ([]byte, error) {
var context homeContext
home.Dependencies.Custom.Update(&context)
home.Dependencies.Custom.Update(&context, nil)
// setup a couple of static things
context.Time = time.Now().UTC()
@ -60,10 +60,10 @@ func (home *Home) homeRender(ctx context.Context) ([]byte, error) {
// render the template
var buffer bytes.Buffer
home.homeTemplate.Get(func() *template.Template {
err = home.homeTemplate.Get(func() *template.Template {
return home.Dependencies.Custom.Template(homeTemplate)
}).Execute(&buffer, context)
return buffer.Bytes(), nil
return buffer.Bytes(), err
}
type homeContext struct {

View file

@ -56,13 +56,13 @@ type legalContext struct {
AssetsDisclaimer string
}
func (legal *Legal) context(r *http.Request) (legalContext, error) {
return legalContext{
BaseContext: legal.Dependencies.Custom.New(),
LegalNotices: cli.LegalNotices,
func (legal *Legal) context(r *http.Request) (lc legalContext, err error) {
legal.Dependencies.Custom.Update(&lc, r)
CSRFCookie: control.CSRFCookie,
SessionCookie: control.SessionCookie,
AssetsDisclaimer: static.AssetsDisclaimer,
}, nil
lc.LegalNotices = cli.LegalNotices
lc.CSRFCookie = control.CSRFCookie
lc.SessionCookie = control.SessionCookie
lc.AssetsDisclaimer = static.AssetsDisclaimer
return
}

View file

@ -1,9 +1,6 @@
{{ template "_base.html" . }}
{{ define "title" }}Legal{{ end }}
{{ define "header/time" }}
<!-- no header/time -->
{{ end }}
{{ define "header"}}
<!-- no header -->
{{ end }}

View file

@ -10,23 +10,23 @@ var AssetsDisclaimer string
// AssetsHome contains assets for the 'Home' entrypoint.
var AssetsHome = Assets{
Scripts: `<script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/static/Home.9f00501f.css"><link rel="stylesheet" href="/static/Home.2353e048.css">`,
Styles: `<link rel="stylesheet" href="/static/Home.4b303448.css"><link rel="stylesheet" href="/static/Home.2353e048.css">`,
}
// AssetsUser contains assets for the 'User' entrypoint.
var AssetsUser = Assets{
Scripts: `<script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/User.4197014b.js"></script><script src="/static/User.30d54198.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/static/Home.9f00501f.css"><link rel="stylesheet" href="/static/User.38d394c2.css">`,
Styles: `<link rel="stylesheet" href="/static/Home.4b303448.css"><link rel="stylesheet" href="/static/User.38d394c2.css">`,
}
// AssetsAdmin contains assets for the 'Admin' entrypoint.
var AssetsAdmin = Assets{
Scripts: `<script nomodule="" defer src="/static/User.30d54198.js"></script><script type="module" src="/static/User.4197014b.js"></script><script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Admin.4ca3cb6f.js"></script><script src="/static/Admin.9750ba9c.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/static/Home.9f00501f.css"><link rel="stylesheet" href="/static/Admin.6d59e220.css"><link rel="stylesheet" href="/static/User.38d394c2.css"><link rel="stylesheet" href="/static/Admin.6d2ae968.css">`,
Styles: `<link rel="stylesheet" href="/static/Home.4b303448.css"><link rel="stylesheet" href="/static/Admin.6d59e220.css"><link rel="stylesheet" href="/static/User.38d394c2.css"><link rel="stylesheet" href="/static/Admin.6d2ae968.css">`,
}
// AssetsLegal contains assets for the 'Legal' entrypoint.
var AssetsLegal = Assets{
Scripts: `<script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Legal.38d394c2.js"></script><script src="/static/Legal.38d394c2.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/static/Home.9f00501f.css"><link rel="stylesheet" href="/static/Legal.d1531eba.css"><link rel="stylesheet" href="/static/Legal.20259812.css">`,
Styles: `<link rel="stylesheet" href="/static/Home.4b303448.css"><link rel="stylesheet" href="/static/Legal.d1531eba.css"><link rel="stylesheet" href="/static/Legal.20259812.css">`,
}

View file

@ -0,0 +1,90 @@
package custom
import (
"html/template"
"net/http"
"reflect"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
"github.com/gorilla/csrf"
"github.com/tkw1536/goprogram/lib/reflectx"
)
// baseContextName is the name of the [BaseContext] type
var baseContextName = reflectx.TypeOf[BaseContext]().Name()
// BaseContext represents a context shared by all the templates.
//
// This context should always be initialized using the [custom.Update], [custom.New] or [custom.NewForm] functions.
// Other invocations might cause an error at runtime.
type BaseContext struct {
inited bool // has this context been inited
GeneratedAt time.Time // time this page was generated at
CSRF template.HTML // CSRF Field
}
// constants that are used in various parts of the template to render stuff
const (
errorPrefix template.HTML = `<div style="z-index:10000;position:fixed;top:0;left:0;width:100vh;height:100vw;background:red;text-align:center;padding:10vh 10vw;font-size:xx-large;font-weight:bold">`
errorSuffix template.HTML = "</div>"
csrfError template.HTML = errorPrefix + "CSRF used but not provided" + errorSuffix
initError template.HTML = errorPrefix + "BaseContext not initialized" + errorSuffix
)
// Use updates this context to use the values from the given base.
// For convenience the passed context is also returned.
func (tc *BaseContext) use(base component.Base, r *http.Request) *BaseContext {
tc.inited = true
tc.GeneratedAt = time.Now().UTC()
// setup the CSRF field
tc.CSRF = csrfError
if r != nil {
tc.CSRF = csrf.TemplateField(r)
}
return tc
}
func (bc BaseContext) DoInitCheck() template.HTML {
if !bc.inited {
return initError
}
return ""
}
// NewForm is like New, but returns a new BaseFormContext
func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request) (ctx BaseFormContext) {
ctx.FormContext = context
ctx.use(custom.Base, r)
return
}
// RenderContext is exactly like NewForm, but returns any to be used as httpx.Form.RenderTemplateContext
func (custom *Custom) RenderContext(ctx httpx.FormContext, r *http.Request) any {
return custom.NewForm(ctx, r)
}
// Update updates an embedded BaseContext field in context.
//
// Assumes that context is a pointer to a struct type.
// If this is not the case, might call panic().
func (custom *Custom) Update(context any, r *http.Request) *BaseContext {
ctx := reflect.ValueOf(context).
Elem().FieldByName(baseContextName).Addr().
Interface().(*BaseContext)
ctx.use(custom.Base, r)
return ctx
}
// BaseFormContext combines BaseContext and FormContext
type BaseFormContext struct {
BaseContext
httpx.FormContext
}

View file

@ -5,4 +5,7 @@ import "github.com/FAU-CDI/wisski-distillery/internal/dis/component"
// Custom implements theme and page customization.
type Custom struct {
component.Base
Dependencies struct {
// nothing yet
}
}

View file

@ -0,0 +1,3 @@
<p>
Generated <time format="{{ .GeneratedAt.Format "2006-01-02T15:04:05Z" }}">{{ .GeneratedAt.Format "2006-01-02T15:04:05Z07:00" }}</time> by <a href="https://github.com/FAU-CDI/wisski-distillery" target="_blank" rel="noopener noreferer">WissKI Distillery</a>. This site might use cookies for essential purposes, see also <a href="/legal/">Legal Notices</a>.
</p>

View file

@ -1,20 +1,15 @@
package custom
import (
_ "embed"
"html/template"
"net/http"
"reflect"
"time"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
"github.com/tkw1536/goprogram/lib/reflectx"
)
const footerName = "footer"
// defaultTemplate is the default footer template
var defaultTemplate = template.Must(template.New("footer.html").Parse(`<p>Powered By WissKI Distillery</p>`))
//go:embed "footer.html"
var defaultTemplateStr string
var defaultTemplate = template.Must(template.New("footer.html").Parse(defaultTemplateStr))
// Template creates a copy of template with shared template parts updated accordingly.
// Any template using this should use one of the template contexts in this package.
@ -25,58 +20,3 @@ func (custom *Custom) Template(tpl *template.Template) *template.Template {
template.Must(clone.AddParseTree(footerName, tree)) // add the parse tree to it
return clone // and return the tree
}
// NewContext returns a new BaseContext
func (custom *Custom) New() (ctx BaseContext) {
ctx.Use(custom.Base)
return
}
// NewForm is like New, but returns a new BaseFormContext
func (custom *Custom) NewForm(context httpx.FormContext) (ctx BaseFormContext) {
ctx.FormContext = context
ctx.Use(custom.Base)
return
}
// RenderContext can be used as httpx.Form.RenderTemplateContext.
// It returns a new [BaseFormContext].
func (custom *Custom) RenderContext(ctx httpx.FormContext, r *http.Request) any {
return BaseFormContext{
FormContext: ctx,
BaseContext: custom.New(),
}
}
// Update updates an embedded BaseContext field in context.
//
// Assumes that context is a pointer to a struct type.
// If this is not the case, might call panic().
func (custom *Custom) Update(context any) *BaseContext {
ctx := reflect.ValueOf(context).
Elem().FieldByName(contextName).Addr().
Interface().(*BaseContext)
ctx.Use(custom.Base)
return ctx
}
// contextName is the name of the [BaseContext] field.
var contextName = reflectx.TypeOf[BaseContext]().Name()
// BaseContext is a context struct shared by all contexts
type BaseContext struct {
Time time.Time // time this page was generated at
}
// Use updates this context to use the values from the given base.
// For convenience the passed context is also returned.
func (tc *BaseContext) Use(base component.Base) *BaseContext {
tc.Time = time.Now().UTC()
return tc
}
// BaseFormContext combines BaseContext and FormContext
type BaseFormContext struct {
BaseContext
httpx.FormContext
}

View file

@ -8,6 +8,20 @@ footer {
margin: 2em;
}
footer {
font-size: small;
border-top: 1px solid black;
padding-top: 1em;
}
footer a {
color: blue !important;
}
time {
font-style: italic;
}
.padding {
padding: 1em;
}

View file

@ -9,14 +9,10 @@
</head>
<body>
{{ .BaseContext.DoInitCheck }}
<header>
<h1 id="top">{{ template "title" . }}</h1>
{{ block "header/time" . }}
{{ if .Time }}
<small>Generated at <code class="date">{{ .Time.Format "2006-01-02T15:04:05Z07:00" }}</code></small>
{{ end }}
{{ end }}
{{ block "header" . }}header{{ end }}
{{ block "header" . }}<!-- no header by default -->{{ end }}
</header>
<main>
<div class="pure-g">
@ -25,11 +21,8 @@
</main>
<footer>
{{ if .Time }}
Generated at <code>{{ .Time }}</code>
{{ end }}
{{ block "footer" . }}
<!-- no footer by default -->
{{ block "footer" .BaseContext }}
<!-- no footer -->
{{ end }}
</footer>

View file

@ -2,7 +2,6 @@
{{ define "title" }}{{ block "form/title" . }}Form{{ end }}{{ end }}
{{ define "header" }}<!-- no header -->{{ end }}
{{ define "header/time" }}<!-- no time -->{{ end }}
{{ define "content" }}
<div class="pure-u-1">
{{ block "form/extra" . }}<!-- no extra -->{{ end }}
@ -24,7 +23,7 @@
{{ block "form/inside" . }}<!-- no inside -->{{ end }}
{{ .Form }}
<input type="submit" value="{{ block " form/button" .}}Submit{{ end }}" class="pure-button">
<input type="submit" value="{{ block "form/button" .}}Submit{{ end }}" class="pure-button">
</fieldset>
</form>
</div>