automatic page title handling
This commit is contained in:
parent
8eb869a971
commit
5ac348a2d2
@ -3,11 +3,8 @@ package routes
|
||||
import (
|
||||
"dk/internal/middleware"
|
||||
"dk/internal/router"
|
||||
"dk/internal/template"
|
||||
"dk/internal/template/components"
|
||||
"fmt"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
"dk/internal/towns"
|
||||
)
|
||||
|
||||
func RegisterTownRoutes(r *router.Router) {
|
||||
@ -19,21 +16,8 @@ func RegisterTownRoutes(r *router.Router) {
|
||||
}
|
||||
|
||||
func showTown(ctx router.Ctx, _ []string) {
|
||||
tmpl, err := template.Cache.Load("town/town.html")
|
||||
if err != nil {
|
||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||
fmt.Fprintf(ctx, "Template error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
content := tmpl.RenderNamed(map[string]any{
|
||||
"town": ctx.UserValue("town"),
|
||||
town := ctx.UserValue("town").(*towns.Town)
|
||||
components.RenderPageTemplate(ctx, town.Name, "town/town.html", map[string]any{
|
||||
"town": town,
|
||||
})
|
||||
|
||||
pageData := components.NewPageData("Town - Dragon Knight", content)
|
||||
if err := components.RenderPage(ctx, pageData, nil); err != nil {
|
||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||
fmt.Fprintf(ctx, "Template error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -3,29 +3,15 @@ package components
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
"dk/internal/auth"
|
||||
"dk/internal/csrf"
|
||||
"dk/internal/middleware"
|
||||
"dk/internal/router"
|
||||
"dk/internal/template"
|
||||
)
|
||||
|
||||
// GenerateTopNav generates the top navigation HTML based on authentication status
|
||||
func GenerateTopNav(ctx router.Ctx) string {
|
||||
if middleware.IsAuthenticated(ctx) {
|
||||
csrfField := csrf.HiddenField(ctx, auth.Manager)
|
||||
return fmt.Sprintf(`<form action="/logout" method="post" class="logout">
|
||||
%s
|
||||
<button class="img-button" type="submit"><img src="/assets/images/button_logout.gif" alt="Log Out" title="Log Out"></button>
|
||||
</form>
|
||||
<a href="/help"><img src="/assets/images/button_help.gif" alt="Help" title="Help"></a>`, csrfField)
|
||||
} else {
|
||||
return `<a href="/login"><img src="/assets/images/button_login.gif" alt="Log In" title="Log In"></a>
|
||||
<a href="/register"><img src="/assets/images/button_register.gif" alt="Register" title="Register"></a>
|
||||
<a href="/help"><img src="/assets/images/button_help.gif" alt="Help" title="Help"></a>`
|
||||
}
|
||||
}
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// PageData holds common page template data
|
||||
type PageData struct {
|
||||
@ -100,3 +86,39 @@ func NewPageData(title, content string) PageData {
|
||||
Build: "dev",
|
||||
}
|
||||
}
|
||||
|
||||
// PageTitle returns a proper title for a rendered page. If an empty string
|
||||
// is given, returns "Dragon Knight". If the provided title already has " - Dragon Knight"
|
||||
// at the end, returns title as-is. Appends " - Dragon Knight" to title otherwise.
|
||||
func PageTitle(title string) string {
|
||||
if title == "" {
|
||||
return "Dragon Knight"
|
||||
}
|
||||
|
||||
if strings.HasSuffix(" - Dragon Knight", title) {
|
||||
return title
|
||||
}
|
||||
|
||||
return title + " - Dragon Knight"
|
||||
}
|
||||
|
||||
// RenderPageTemplate is a simplified helper that renders a template within the page layout.
|
||||
// It loads the template, renders it with the provided data, and then renders the full page.
|
||||
// Returns true if successful, false if an error occurred (error is written to response).
|
||||
func RenderPageTemplate(ctx router.Ctx, title, templateName string, data map[string]any) bool {
|
||||
content, err := template.RenderNamed(templateName, data)
|
||||
if err != nil {
|
||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||
fmt.Fprintf(ctx, "Template error: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
pageData := NewPageData(PageTitle(title), content)
|
||||
if err := RenderPage(ctx, pageData, nil); err != nil {
|
||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||
fmt.Fprintf(ctx, "Template error: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
24
internal/template/components/topnav.go
Normal file
24
internal/template/components/topnav.go
Normal file
@ -0,0 +1,24 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"dk/internal/auth"
|
||||
"dk/internal/csrf"
|
||||
"dk/internal/middleware"
|
||||
"dk/internal/router"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GenerateTopNav generates the top navigation HTML based on authentication status
|
||||
func GenerateTopNav(ctx router.Ctx) string {
|
||||
if middleware.IsAuthenticated(ctx) {
|
||||
return fmt.Sprintf(`<form action="/logout" method="post" class="logout">
|
||||
%s
|
||||
<button class="img-button" type="submit"><img src="/assets/images/button_logout.gif" alt="Log Out" title="Log Out"></button>
|
||||
</form>
|
||||
<a href="/help"><img src="/assets/images/button_help.gif" alt="Help" title="Help"></a>`, csrf.HiddenField(ctx, auth.Manager))
|
||||
} else {
|
||||
return `<a href="/login"><img src="/assets/images/button_login.gif" alt="Log In" title="Log In"></a>
|
||||
<a href="/register"><img src="/assets/images/button_register.gif" alt="Register" title="Register"></a>
|
||||
<a href="/help"><img src="/assets/images/button_help.gif" alt="Help" title="Help"></a>`
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ type TemplateCache struct {
|
||||
|
||||
type RenderOptions struct {
|
||||
ResolveIncludes bool
|
||||
Blocks map[string]string
|
||||
Blocks map[string]string
|
||||
}
|
||||
|
||||
type Template struct {
|
||||
@ -261,7 +261,7 @@ func (t *Template) getStructField(obj any, fieldName string) any {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
rv := reflect.ValueOf(obj)
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
if rv.IsNil() {
|
||||
@ -269,16 +269,16 @@ func (t *Template) getStructField(obj any, fieldName string) any {
|
||||
}
|
||||
rv = rv.Elem()
|
||||
}
|
||||
|
||||
|
||||
if rv.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
field := rv.FieldByName(fieldName)
|
||||
if !field.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
return field.Interface()
|
||||
}
|
||||
|
||||
@ -314,29 +314,29 @@ func (t *Template) WriteToWithOptions(ctx *fasthttp.RequestCtx, data any, opts R
|
||||
// processIncludes handles {include "template.html"} directives
|
||||
func (t *Template) processIncludes(content string, data map[string]any, opts RenderOptions) string {
|
||||
result := content
|
||||
|
||||
|
||||
for {
|
||||
start := strings.Index(result, "{include ")
|
||||
if start == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
end := strings.Index(result[start:], "}")
|
||||
if end == -1 {
|
||||
break
|
||||
}
|
||||
end += start
|
||||
|
||||
directive := result[start+9:end] // Skip "{include "
|
||||
|
||||
directive := result[start+9 : end] // Skip "{include "
|
||||
templateName := strings.Trim(directive, "\" ")
|
||||
|
||||
|
||||
if includedTemplate, err := t.cache.Load(templateName); err == nil {
|
||||
var includedContent string
|
||||
if data != nil {
|
||||
// Create new options to pass blocks to included template
|
||||
includeOpts := RenderOptions{
|
||||
ResolveIncludes: opts.ResolveIncludes,
|
||||
Blocks: opts.Blocks,
|
||||
Blocks: opts.Blocks,
|
||||
}
|
||||
includedContent = includedTemplate.RenderNamedWithOptions(includeOpts, data)
|
||||
} else {
|
||||
@ -348,7 +348,7 @@ func (t *Template) processIncludes(content string, data map[string]any, opts Ren
|
||||
result = result[:start] + result[end+1:]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@ -357,16 +357,16 @@ func (t *Template) processYield(content string, opts RenderOptions) string {
|
||||
if opts.Blocks == nil {
|
||||
return strings.ReplaceAll(content, "{yield}", "")
|
||||
}
|
||||
|
||||
|
||||
result := content
|
||||
for blockName, blockContent := range opts.Blocks {
|
||||
yieldPlaceholder := fmt.Sprintf("{yield %s}", blockName)
|
||||
result = strings.ReplaceAll(result, yieldPlaceholder, blockContent)
|
||||
}
|
||||
|
||||
|
||||
// Replace any remaining {yield} with empty string
|
||||
result = strings.ReplaceAll(result, "{yield}", "")
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@ -375,23 +375,23 @@ func (t *Template) processBlocks(content string, opts *RenderOptions) string {
|
||||
if opts.Blocks == nil {
|
||||
opts.Blocks = make(map[string]string)
|
||||
}
|
||||
|
||||
|
||||
result := content
|
||||
|
||||
|
||||
for {
|
||||
start := strings.Index(result, "{block ")
|
||||
if start == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
nameEnd := strings.Index(result[start:], "}")
|
||||
if nameEnd == -1 {
|
||||
break
|
||||
}
|
||||
nameEnd += start
|
||||
|
||||
|
||||
blockName := strings.Trim(result[start+7:nameEnd], "\" ")
|
||||
|
||||
|
||||
contentStart := nameEnd + 1
|
||||
endTag := "{/block}"
|
||||
contentEnd := strings.Index(result[contentStart:], endTag)
|
||||
@ -399,13 +399,38 @@ func (t *Template) processBlocks(content string, opts *RenderOptions) string {
|
||||
break
|
||||
}
|
||||
contentEnd += contentStart
|
||||
|
||||
|
||||
blockContent := result[contentStart:contentEnd]
|
||||
opts.Blocks[blockName] = blockContent
|
||||
|
||||
|
||||
// Remove the block definition from the template
|
||||
result = result[:start] + result[contentEnd+len(endTag):]
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// RenderToContext is a simplified helper that renders a template and writes it to the request context
|
||||
// with error handling. Returns true if successful, false if an error occurred (error is written to response).
|
||||
func RenderToContext(ctx *fasthttp.RequestCtx, templateName string, data map[string]any) bool {
|
||||
tmpl, err := Cache.Load(templateName)
|
||||
if err != nil {
|
||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||
fmt.Fprintf(ctx, "Template error: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
tmpl.WriteTo(ctx, data)
|
||||
return true
|
||||
}
|
||||
|
||||
// RenderNamed is a simplified helper that loads and renders a template with the given data,
|
||||
// returning the rendered content or an error.
|
||||
func RenderNamed(templateName string, data map[string]any) (string, error) {
|
||||
tmpl, err := Cache.Load(templateName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to load template %s: %w", templateName, err)
|
||||
}
|
||||
|
||||
return tmpl.RenderNamed(data), nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user