automatic page title handling

This commit is contained in:
Sky Johnson 2025-08-11 10:59:13 -05:00
parent 8eb869a971
commit 5ac348a2d2
4 changed files with 115 additions and 60 deletions

View File

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

View File

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

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

View File

@ -23,7 +23,7 @@ type TemplateCache struct {
type RenderOptions struct {
ResolveIncludes bool
Blocks map[string]string
Blocks map[string]string
}
type Template struct {
@ -327,7 +327,7 @@ func (t *Template) processIncludes(content string, data map[string]any, opts Ren
}
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 {
@ -336,7 +336,7 @@ func (t *Template) processIncludes(content string, data map[string]any, opts Ren
// 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 {
@ -409,3 +409,28 @@ func (t *Template) processBlocks(content string, opts *RenderOptions) string {
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
}