Add templating
This commit is contained in:
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -36,7 +37,7 @@ type Config struct {
|
||||
// Server hosts the Luxtools HTTP handlers.
|
||||
type Server struct {
|
||||
mux *http.ServeMux
|
||||
indexPage []byte
|
||||
templates *template.Template
|
||||
staticFS fs.FS
|
||||
mediaFS http.FileSystem
|
||||
|
||||
@@ -60,9 +61,10 @@ func New(cfg Config) (*Server, error) {
|
||||
return nil, fmt.Errorf("media directory must be a directory: %s", mediaDir)
|
||||
}
|
||||
|
||||
indexPage, err := webbundle.Content.ReadFile("templates/index.html")
|
||||
// Parse all templates
|
||||
tmpl, err := template.ParseFS(webbundle.Content, "templates/*.html")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read index template: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse templates: %w", err)
|
||||
}
|
||||
|
||||
staticFS, err := fs.Sub(webbundle.Content, "static")
|
||||
@@ -72,7 +74,7 @@ func New(cfg Config) (*Server, error) {
|
||||
|
||||
s := &Server{
|
||||
mux: http.NewServeMux(),
|
||||
indexPage: indexPage,
|
||||
templates: tmpl,
|
||||
staticFS: staticFS,
|
||||
mediaFS: http.Dir(mediaDir),
|
||||
}
|
||||
@@ -103,7 +105,12 @@ func (s *Server) registerRoutes() {
|
||||
func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/":
|
||||
s.sendHTML(w, http.StatusOK, s.indexPage)
|
||||
data := DefaultMenuBar()
|
||||
s.renderTemplate(w, http.StatusOK, "index.html", data)
|
||||
case "/admin":
|
||||
data := AdminMenuBar()
|
||||
data.Title = "Admin Panel"
|
||||
s.renderTemplate(w, http.StatusOK, "admin.html", data)
|
||||
case "/counter":
|
||||
s.handleCounter(w, r)
|
||||
case "/time":
|
||||
@@ -127,6 +134,16 @@ func (s *Server) handleCounter(w http.ResponseWriter, r *http.Request) {
|
||||
s.sendHTMLString(w, http.StatusOK, renderCounter(count))
|
||||
}
|
||||
|
||||
func (s *Server) renderTemplate(w http.ResponseWriter, status int, name string, data interface{}) {
|
||||
w.Header().Set("Content-Type", htmlContentType)
|
||||
w.WriteHeader(status)
|
||||
|
||||
if err := s.templates.ExecuteTemplate(w, name, data); err != nil {
|
||||
log.Printf("template execution failed: %v", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) sendHTML(w http.ResponseWriter, status int, body []byte) {
|
||||
w.Header().Set("Content-Type", htmlContentType)
|
||||
w.WriteHeader(status)
|
||||
|
||||
86
internal/server/template_data.go
Normal file
86
internal/server/template_data.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package server
|
||||
|
||||
import "html/template"
|
||||
|
||||
// This file defines the data structures and helper functions for the template system.
|
||||
//
|
||||
// The template system uses Go's html/template package to provide:
|
||||
// - Dynamic menu bars that can be customized per page
|
||||
// - Template inheritance via layout.html
|
||||
// - Type-safe data passing via structs
|
||||
//
|
||||
// To create a new page with a custom menu:
|
||||
// 1. Create a PageData struct with your menu configuration
|
||||
// 2. Call s.renderTemplate(w, status, "yourpage.html", data)
|
||||
// 3. In yourpage.html, use {{template "layout" .}} and define content blocks
|
||||
//
|
||||
// See TEMPLATE_GUIDE.md for detailed examples and usage patterns.
|
||||
|
||||
// PageData holds the data passed to page templates.
|
||||
type PageData struct {
|
||||
Title string
|
||||
MenuGroups []MenuGroup
|
||||
ShowClock bool
|
||||
}
|
||||
|
||||
// MenuGroup represents a dropdown menu in the navbar.
|
||||
type MenuGroup struct {
|
||||
LabelHTML template.HTML
|
||||
Items []MenuItem
|
||||
}
|
||||
|
||||
// MenuItem represents a single menu item.
|
||||
type MenuItem struct {
|
||||
LabelHTML template.HTML
|
||||
URL string
|
||||
IsDivider bool
|
||||
}
|
||||
|
||||
// DefaultMenuBar returns the standard menu configuration.
|
||||
func DefaultMenuBar() PageData {
|
||||
return PageData{
|
||||
ShowClock: true,
|
||||
MenuGroups: []MenuGroup{
|
||||
{
|
||||
LabelHTML: template.HTML(`<span class="red-168-text">F</span>ile`),
|
||||
Items: []MenuItem{
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">N</span>ew`), URL: "#!"},
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">O</span>pen`), URL: "#!"},
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">S</span>ave`), URL: "#!"},
|
||||
{LabelHTML: template.HTML(`Save <span class="red-168-text">A</span>s`), URL: "#!"},
|
||||
{IsDivider: true},
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">E</span>xit`), URL: "#!"},
|
||||
},
|
||||
},
|
||||
{
|
||||
LabelHTML: template.HTML(`<span class="red-168-text">E</span>dit`),
|
||||
Items: []MenuItem{
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">C</span>ut`), URL: "#!"},
|
||||
{LabelHTML: template.HTML(`C<span class="red-168-text">o</span>py`), URL: "#!"},
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">P</span>aste`), URL: "#!"},
|
||||
},
|
||||
},
|
||||
{
|
||||
LabelHTML: template.HTML(`<span class="red-168-text">H</span>elp`),
|
||||
Items: []MenuItem{
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">D</span>ocumentation`), URL: "#!"},
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">A</span>bout`), URL: "#!"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// AdminMenuBar returns a menu configuration with admin options.
|
||||
func AdminMenuBar() PageData {
|
||||
data := DefaultMenuBar()
|
||||
data.MenuGroups = append(data.MenuGroups, MenuGroup{
|
||||
LabelHTML: template.HTML(`<span class="red-168-text">A</span>dmin`),
|
||||
Items: []MenuItem{
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">U</span>sers`), URL: "/admin/users"},
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">S</span>ettings`), URL: "/admin/settings"},
|
||||
{LabelHTML: template.HTML(`<span class="red-168-text">L</span>ogs`), URL: "/admin/logs"},
|
||||
},
|
||||
})
|
||||
return data
|
||||
}
|
||||
Reference in New Issue
Block a user