footer: Add a semi-flexible template system
This commit is contained in:
parent
bda763725e
commit
021fc3cc7e
26 changed files with 239 additions and 208 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +1,20 @@
|
||||||
{{ template "_form.html" . }}
|
{{ template "_form.html" . }}
|
||||||
{{ define "form/title" }}Login Required{{ end }}
|
{{ define "form/title" }}Login Required{{ end }}
|
||||||
{{ define "form/button" }}Login{{ end }}
|
{{ define "form/button" }}Login{{ end }}
|
||||||
|
{{ define "form/inside" }}
|
||||||
|
<div class="pure-form-group">
|
||||||
|
<p>
|
||||||
|
The site you are attempting to access requires you to login with your account.
|
||||||
|
Simply fill out the form below and click <em>Login</em>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<small>
|
||||||
|
This functionality requires you to have cookies enabled.
|
||||||
|
A cookie to prevent fraudulent login attempts has already been set on your machine.
|
||||||
|
When clicking the <em>Login</em> button, a further cookie will be set on your computer to keep track of your login session.
|
||||||
|
If you do not wish to have futher cookie(s) set, do not click Login and do not attempt to access protected sites.
|
||||||
|
See also <a href="/legal/#cookies">Legal Notices</a>.
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
@ -79,7 +79,7 @@ func (panel *UserPanel) UserFormContext(ctx httpx.FormContext, r *http.Request)
|
||||||
user, err := panel.Dependencies.Auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
|
|
||||||
uctx := userFormContext{FormContext: ctx}
|
uctx := userFormContext{FormContext: ctx}
|
||||||
panel.Dependencies.Custom.Update(&uctx)
|
panel.Dependencies.Custom.Update(&uctx, r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
uctx.User = &user.User
|
uctx.User = &user.User
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
FormContext: context,
|
FormContext: context,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
panel.Dependencies.Custom.Update(&ctx.userFormContext)
|
panel.Dependencies.Custom.Update(&ctx.userFormContext, r)
|
||||||
|
|
||||||
if err == nil && user != nil {
|
if err == nil && user != nil {
|
||||||
ctx.userFormContext.User = &user.User
|
ctx.userFormContext.User = &user.User
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
||||||
userTemplate := panel.Dependencies.Custom.Template(userTemplate)
|
userTemplate := panel.Dependencies.Custom.Template(userTemplate)
|
||||||
return &httpx.HTMLHandler[routeUserContext]{
|
return &httpx.HTMLHandler[routeUserContext]{
|
||||||
Handler: func(r *http.Request) (ruc routeUserContext, err error) {
|
Handler: func(r *http.Request) (ruc routeUserContext, err error) {
|
||||||
panel.Dependencies.Custom.Update(&ruc)
|
panel.Dependencies.Custom.Update(&ruc, r)
|
||||||
ruc.AuthUser, err = panel.Dependencies.Auth.UserOf(r)
|
ruc.AuthUser, err = panel.Dependencies.Auth.UserOf(r)
|
||||||
return routeUserContext{}, err
|
return ruc, err
|
||||||
},
|
},
|
||||||
Template: userTemplate,
|
Template: userTemplate,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler {
|
||||||
if context.Err != nil {
|
if context.Err != nil {
|
||||||
context.Err = errLoginFailed
|
context.Err = errLoginFailed
|
||||||
}
|
}
|
||||||
httpx.WriteHTML(auth.Dependencies.Custom.NewForm(context), nil, loginTemplate, "", w, r)
|
httpx.WriteHTML(auth.Dependencies.Custom.NewForm(context, r), nil, loginTemplate, "", w, r)
|
||||||
},
|
},
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) {
|
Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ type componentContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) components(r *http.Request) (cp componentContext, err error) {
|
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
|
cp.Analytics = *admin.Analytics
|
||||||
return
|
return
|
||||||
|
|
@ -49,7 +49,7 @@ type ingredientsContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) ingredients(r *http.Request) (cp ingredientsContext, err error) {
|
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!
|
// find the instance itself!
|
||||||
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), mux.Vars(r)["slug"])
|
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), mux.Vars(r)["slug"])
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ type indexContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) index(r *http.Request) (idx indexContext, err error) {
|
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)
|
idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"html/template"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
|
|
@ -25,13 +24,12 @@ var instanceTemplate = static.AssetsAdmin.MustParseShared(
|
||||||
type instanceContext struct {
|
type instanceContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
||||||
CSRF template.HTML
|
|
||||||
Instance models.Instance
|
Instance models.Instance
|
||||||
Info status.WissKI
|
Info status.WissKI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) instance(r *http.Request) (is instanceContext, err error) {
|
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)
|
is.CSRF = csrf.TemplateField(r)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package admin
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"html/template"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
@ -27,12 +26,11 @@ type userContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
httpx.FormContext
|
httpx.FormContext
|
||||||
|
|
||||||
CSRF template.HTML
|
|
||||||
Users []*auth.AuthUser
|
Users []*auth.AuthUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) users(r *http.Request) (uc userContext, err error) {
|
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.CSRF = csrf.TemplateField(r)
|
||||||
uc.Users, err = admin.Dependencies.Auth.Users(r.Context())
|
uc.Users, err = admin.Dependencies.Auth.Users(r.Context())
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}WissKI Distillery{{ end }}
|
{{ define "title" }}WissKI Distillery{{ end }}
|
||||||
|
|
||||||
{{ define "header/time" }}
|
|
||||||
<!-- no header/time -->
|
|
||||||
{{ end }}
|
|
||||||
{{ define "header"}}
|
{{ define "header"}}
|
||||||
<!-- no header -->
|
<!-- no header -->
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ var homeTemplate = static.AssetsHome.MustParseShared("home.html", homeHTMLStr)
|
||||||
|
|
||||||
func (home *Home) homeRender(ctx context.Context) ([]byte, error) {
|
func (home *Home) homeRender(ctx context.Context) ([]byte, error) {
|
||||||
var context homeContext
|
var context homeContext
|
||||||
home.Dependencies.Custom.Update(&context)
|
home.Dependencies.Custom.Update(&context, nil)
|
||||||
|
|
||||||
// setup a couple of static things
|
// setup a couple of static things
|
||||||
context.Time = time.Now().UTC()
|
context.Time = time.Now().UTC()
|
||||||
|
|
@ -60,10 +60,10 @@ func (home *Home) homeRender(ctx context.Context) ([]byte, error) {
|
||||||
|
|
||||||
// render the template
|
// render the template
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
home.homeTemplate.Get(func() *template.Template {
|
err = home.homeTemplate.Get(func() *template.Template {
|
||||||
return home.Dependencies.Custom.Template(homeTemplate)
|
return home.Dependencies.Custom.Template(homeTemplate)
|
||||||
}).Execute(&buffer, context)
|
}).Execute(&buffer, context)
|
||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
type homeContext struct {
|
type homeContext struct {
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,13 @@ type legalContext struct {
|
||||||
AssetsDisclaimer string
|
AssetsDisclaimer string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (legal *Legal) context(r *http.Request) (legalContext, error) {
|
func (legal *Legal) context(r *http.Request) (lc legalContext, err error) {
|
||||||
return legalContext{
|
legal.Dependencies.Custom.Update(&lc, r)
|
||||||
BaseContext: legal.Dependencies.Custom.New(),
|
|
||||||
LegalNotices: cli.LegalNotices,
|
|
||||||
|
|
||||||
CSRFCookie: control.CSRFCookie,
|
lc.LegalNotices = cli.LegalNotices
|
||||||
SessionCookie: control.SessionCookie,
|
|
||||||
AssetsDisclaimer: static.AssetsDisclaimer,
|
lc.CSRFCookie = control.CSRFCookie
|
||||||
}, nil
|
lc.SessionCookie = control.SessionCookie
|
||||||
|
lc.AssetsDisclaimer = static.AssetsDisclaimer
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Legal{{ end }}
|
{{ define "title" }}Legal{{ end }}
|
||||||
|
|
||||||
{{ define "header/time" }}
|
|
||||||
<!-- no header/time -->
|
|
||||||
{{ end }}
|
|
||||||
{{ define "header"}}
|
{{ define "header"}}
|
||||||
<!-- no header -->
|
<!-- no header -->
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
||||||
|
|
@ -10,23 +10,23 @@ var AssetsDisclaimer string
|
||||||
// AssetsHome contains assets for the 'Home' entrypoint.
|
// AssetsHome contains assets for the 'Home' entrypoint.
|
||||||
var AssetsHome = Assets{
|
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>`,
|
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.
|
// AssetsUser contains assets for the 'User' entrypoint.
|
||||||
var AssetsUser = Assets{
|
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>`,
|
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.
|
// AssetsAdmin contains assets for the 'Admin' entrypoint.
|
||||||
var AssetsAdmin = Assets{
|
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>`,
|
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.
|
// AssetsLegal contains assets for the 'Legal' entrypoint.
|
||||||
var AssetsLegal = Assets{
|
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>`,
|
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">`,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
90
internal/dis/component/control/static/custom/context.go
Normal file
90
internal/dis/component/control/static/custom/context.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -5,4 +5,7 @@ import "github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
// Custom implements theme and page customization.
|
// Custom implements theme and page customization.
|
||||||
type Custom struct {
|
type Custom struct {
|
||||||
component.Base
|
component.Base
|
||||||
|
Dependencies struct {
|
||||||
|
// nothing yet
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
internal/dis/component/control/static/custom/footer.html
Normal file
3
internal/dis/component/control/static/custom/footer.html
Normal 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>
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
package custom
|
package custom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
"html/template"
|
"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"
|
const footerName = "footer"
|
||||||
|
|
||||||
// defaultTemplate is the default footer template
|
//go:embed "footer.html"
|
||||||
var defaultTemplate = template.Must(template.New("footer.html").Parse(`<p>Powered By WissKI Distillery</p>`))
|
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.
|
// 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.
|
// 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
|
template.Must(clone.AddParseTree(footerName, tree)) // add the parse tree to it
|
||||||
return clone // and return the tree
|
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -8,6 +8,20 @@ footer {
|
||||||
margin: 2em;
|
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 {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,10 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
{{ .BaseContext.DoInitCheck }}
|
||||||
<header>
|
<header>
|
||||||
<h1 id="top">{{ template "title" . }}</h1>
|
<h1 id="top">{{ template "title" . }}</h1>
|
||||||
{{ block "header/time" . }}
|
{{ block "header" . }}<!-- no header by default -->{{ end }}
|
||||||
{{ if .Time }}
|
|
||||||
<small>Generated at <code class="date">{{ .Time.Format "2006-01-02T15:04:05Z07:00" }}</code></small>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ block "header" . }}header{{ end }}
|
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
|
|
@ -25,11 +21,8 @@
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
{{ if .Time }}
|
{{ block "footer" .BaseContext }}
|
||||||
Generated at <code>{{ .Time }}</code>
|
<!-- no footer -->
|
||||||
{{ end }}
|
|
||||||
{{ block "footer" . }}
|
|
||||||
<!-- no footer by default -->
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
{{ define "title" }}{{ block "form/title" . }}Form{{ end }}{{ end }}
|
{{ define "title" }}{{ block "form/title" . }}Form{{ end }}{{ end }}
|
||||||
|
|
||||||
{{ define "header" }}<!-- no header -->{{ end }}
|
{{ define "header" }}<!-- no header -->{{ end }}
|
||||||
{{ define "header/time" }}<!-- no time -->{{ end }}
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
{{ block "form/extra" . }}<!-- no extra -->{{ end }}
|
{{ block "form/extra" . }}<!-- no extra -->{{ end }}
|
||||||
|
|
@ -24,7 +23,7 @@
|
||||||
|
|
||||||
{{ block "form/inside" . }}<!-- no inside -->{{ end }}
|
{{ block "form/inside" . }}<!-- no inside -->{{ end }}
|
||||||
{{ .Form }}
|
{{ .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>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,23 @@ package httpx
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteHTML writes a html response of type T to w.
|
// WriteHTML writes a html response of type T to w.
|
||||||
// If an error occured, writes an error response instead.
|
// If an error occured, writes an error response instead.
|
||||||
func WriteHTML[T any](result T, err error, template *template.Template, templateName string, w http.ResponseWriter, r *http.Request) {
|
func WriteHTML[T any](result T, err error, template *template.Template, templateName string, w http.ResponseWriter, r *http.Request) (e error) {
|
||||||
|
// log any error that occurs;
|
||||||
|
defer func() {
|
||||||
|
if e != nil {
|
||||||
|
zerolog.Ctx(r.Context()).Err(e).Str("path", r.URL.String()).Msg("error rendering template")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// intercept any errors
|
// intercept any errors
|
||||||
if HTMLInterceptor.Intercept(w, r, err) {
|
if HTMLInterceptor.Intercept(w, r, err) {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// write out the response as html
|
// write out the response as html
|
||||||
|
|
@ -23,9 +32,9 @@ func WriteHTML[T any](result T, err error, template *template.Template, template
|
||||||
|
|
||||||
// and return the template
|
// and return the template
|
||||||
if templateName != "" {
|
if templateName != "" {
|
||||||
template.ExecuteTemplate(minifier, templateName, result)
|
return template.ExecuteTemplate(minifier, templateName, result)
|
||||||
} else {
|
} else {
|
||||||
template.Execute(minifier, result)
|
return template.Execute(minifier, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build nominify
|
||||||
|
|
||||||
package httpx
|
package httpx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
27
pkg/httpx/html_minify_off.go
Normal file
27
pkg/httpx/html_minify_off.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
//go:build !nominify
|
||||||
|
|
||||||
|
package httpx
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// MinifyHTMLWriter wraps the given io.Writer to minify the given html instead.
|
||||||
|
// The writer must be closed explicitly.
|
||||||
|
//
|
||||||
|
// Specific environments may chose to disable http minification, in which case MinifyHTMLWriter becomes the identity function.
|
||||||
|
func MinifyHTMLWriter(dest io.Writer) io.WriteCloser {
|
||||||
|
return noop{Writer: dest}
|
||||||
|
}
|
||||||
|
|
||||||
|
type noop struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (noop) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinifyHTML minifies the html source.
|
||||||
|
// If an error occurs, returns the unmodified source instead.
|
||||||
|
func MinifyHTML(source []byte) []byte {
|
||||||
|
return source
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue