static embed, html rendering and htmx
- commit
- b04c405e23bd7ad66421515aa36cb1ba1e8590c0
- author
- bnema · · 2023-09-01 18:34 UTC
- parent
- ec092c3
- signature
- unsigned
10 files · +180 -41
M cmd/main.go +68 -6
| @@ -1,21 +1,83 @@ | |||
| 1 | 1 | package main | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "fmt" | |
| 5 | + | "html/template" | |
| 4 | 6 | "net/http" | |
| 7 | + | "strings" | |
| 5 | 8 | ||
| 6 | 9 | "github.com/labstack/echo/v4" | |
| 7 | 10 | "gogs.bnema.dev/gordon-echo/internal/ui" | |
| 11 | + | "gogs.bnema.dev/gordon-echo/pkg/utils" | |
| 8 | 12 | ) | |
| 9 | 13 | ||
| 10 | - | func main() { | |
| 11 | - | e := echo.New() | |
| 14 | + | const trailedAdminPath = "/admin/" | |
| 15 | + | ||
| 16 | + | // bindStaticAdminUI serves the static admin UI | |
| 17 | + | func bindStaticAdminUI(e *echo.Echo) error { | |
| 18 | + | tmpl, err := template.New("index").ParseFS(ui.PublicFS, "index.html") | |
| 19 | + | if err != nil { | |
| 20 | + | return fmt.Errorf("failed to parse index.html: %w", err) | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | renderer := &utils.Renderer{ | |
| 24 | + | Template: tmpl, | |
| 25 | + | ParseError: err, | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | // redirect to trailing slash to ensure that relative urls will still work properly | |
| 29 | + | e.GET( | |
| 30 | + | strings.TrimRight(trailedAdminPath, "/"), | |
| 31 | + | func(c echo.Context) error { | |
| 32 | + | return c.Redirect(http.StatusTemporaryRedirect, strings.TrimLeft(trailedAdminPath, "/")) | |
| 33 | + | }, | |
| 34 | + | ) | |
| 35 | + | ||
| 36 | + | // Main handler for /admin/ | |
| 37 | + | e.GET(trailedAdminPath, func(c echo.Context) error { | |
| 38 | + | data := map[string]interface{}{ | |
| 39 | + | "website": map[string]interface{}{ | |
| 40 | + | "title": "Hello, World!", | |
| 41 | + | }, | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | html, err := renderer.Render(data) | |
| 45 | + | if err != nil { | |
| 46 | + | return c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) | |
| 47 | + | } | |
| 12 | 48 | ||
| 13 | - | e.GET("/", func(c echo.Context) error { | |
| 14 | - | // render the internal/ui/templates/index.html template | |
| 15 | - | return ui.Render(c, "index.html", nil) | |
| 49 | + | return c.HTML(http.StatusOK, html) | |
| 16 | 50 | }) | |
| 51 | + | // serves static files from the /ui/dist directory | |
| 52 | + | // (similar to echo.StaticFS but with gzip middleware enabled) | |
| 53 | + | e.GET( | |
| 54 | + | trailedAdminPath+"*", | |
| 55 | + | echo.StaticDirectoryHandler(ui.PublicFS, false), | |
| 56 | + | ) | |
| 57 | + | e.GET("/htmx", func(c echo.Context) error { | |
| 58 | + | fragment := c.Request().Header.Get("X-Fragment") | |
| 59 | + | ||
| 60 | + | if fragment == "hello" { | |
| 61 | + | // Fetch and return the "hello" fragment | |
| 62 | + | content, err := utils.GetHTMLFragmentByID("hello") | |
| 63 | + | fmt.Println("X-Fragment:", c.Request().Header.Get("X-Fragment")) | |
| 64 | + | if err != nil { | |
| 65 | + | return c.String(http.StatusInternalServerError, err.Error()) | |
| 66 | + | } | |
| 67 | + | return c.HTML(http.StatusOK, content) | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | // Handle other fragments or default behavior | |
| 71 | + | return c.String(http.StatusBadRequest, "Invalid fragment") | |
| 72 | + | }) | |
| 73 | + | return nil | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | func main() { | |
| 77 | + | e := echo.New() | |
| 17 | 78 | ||
| 18 | - | e.GET("/public/*", echo.WrapHandler(http.FileServer(http.FS(ui.PublicFS)))) | |
| 79 | + | // use func bindStaticAdminUI to serve the static admin UI | |
| 80 | + | bindStaticAdminUI(e) | |
| 19 | 81 | ||
| 20 | 82 | e.Logger.Fatal(e.Start(":1323")) | |
| 21 | 83 | ||
| @@ -1,21 +1,83 @@ | |||||
| 1 | package main | 1 | package main | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "fmt" | |||
| 5 | + | "html/template" | |||
| 4 | "net/http" | 6 | "net/http" | ||
| 7 | + | "strings" | |||
| 5 | 8 | ||||
| 6 | "github.com/labstack/echo/v4" | 9 | "github.com/labstack/echo/v4" | ||
| 7 | "gogs.bnema.dev/gordon-echo/internal/ui" | 10 | "gogs.bnema.dev/gordon-echo/internal/ui" | ||
| 11 | + | "gogs.bnema.dev/gordon-echo/pkg/utils" | |||
| 8 | ) | 12 | ) | ||
| 9 | 13 | ||||
| 10 | - | func main() { | 14 | + | const trailedAdminPath = "/admin/" |
| 11 | - | e := echo.New() | 15 | + | |
| 16 | + | // bindStaticAdminUI serves the static admin UI | |||
| 17 | + | func bindStaticAdminUI(e *echo.Echo) error { | |||
| 18 | + | tmpl, err := template.New("index").ParseFS(ui.PublicFS, "index.html") | |||
| 19 | + | if err != nil { | |||
| 20 | + | return fmt.Errorf("failed to parse index.html: %w", err) | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | renderer := &utils.Renderer{ | |||
| 24 | + | Template: tmpl, | |||
| 25 | + | ParseError: err, | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | // redirect to trailing slash to ensure that relative urls will still work properly | |||
| 29 | + | e.GET( | |||
| 30 | + | strings.TrimRight(trailedAdminPath, "/"), | |||
| 31 | + | func(c echo.Context) error { | |||
| 32 | + | return c.Redirect(http.StatusTemporaryRedirect, strings.TrimLeft(trailedAdminPath, "/")) | |||
| 33 | + | }, | |||
| 34 | + | ) | |||
| 35 | + | ||||
| 36 | + | // Main handler for /admin/ | |||
| 37 | + | e.GET(trailedAdminPath, func(c echo.Context) error { | |||
| 38 | + | data := map[string]interface{}{ | |||
| 39 | + | "website": map[string]interface{}{ | |||
| 40 | + | "title": "Hello, World!", | |||
| 41 | + | }, | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | html, err := renderer.Render(data) | |||
| 45 | + | if err != nil { | |||
| 46 | + | return c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to render template: %v", err)) | |||
| 47 | + | } | |||
| 12 | 48 | ||||
| 13 | - | e.GET("/", func(c echo.Context) error { | 49 | + | return c.HTML(http.StatusOK, html) |
| 14 | - | // render the internal/ui/templates/index.html template | |||
| 15 | - | return ui.Render(c, "index.html", nil) | |||
| 16 | }) | 50 | }) | ||
| 51 | + | // serves static files from the /ui/dist directory | |||
| 52 | + | // (similar to echo.StaticFS but with gzip middleware enabled) | |||
| 53 | + | e.GET( | |||
| 54 | + | trailedAdminPath+"*", | |||
| 55 | + | echo.StaticDirectoryHandler(ui.PublicFS, false), | |||
| 56 | + | ) | |||
| 57 | + | e.GET("/htmx", func(c echo.Context) error { | |||
| 58 | + | fragment := c.Request().Header.Get("X-Fragment") | |||
| 59 | + | ||||
| 60 | + | if fragment == "hello" { | |||
| 61 | + | // Fetch and return the "hello" fragment | |||
| 62 | + | content, err := utils.GetHTMLFragmentByID("hello") | |||
| 63 | + | fmt.Println("X-Fragment:", c.Request().Header.Get("X-Fragment")) | |||
| 64 | + | if err != nil { | |||
| 65 | + | return c.String(http.StatusInternalServerError, err.Error()) | |||
| 66 | + | } | |||
| 67 | + | return c.HTML(http.StatusOK, content) | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | // Handle other fragments or default behavior | |||
| 71 | + | return c.String(http.StatusBadRequest, "Invalid fragment") | |||
| 72 | + | }) | |||
| 73 | + | return nil | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | func main() { | |||
| 77 | + | e := echo.New() | |||
| 17 | 78 | ||||
| 18 | - | e.GET("/public/*", echo.WrapHandler(http.FileServer(http.FS(ui.PublicFS)))) | 79 | + | // use func bindStaticAdminUI to serve the static admin UI |
| 80 | + | bindStaticAdminUI(e) | |||
| 19 | 81 | ||||
| 20 | e.Logger.Fatal(e.Start(":1323")) | 82 | e.Logger.Fatal(e.Start(":1323")) | ||
| 21 | 83 | ||||
M go.mod +7 -0
| @@ -5,6 +5,13 @@ go 1.21.0 | |||
| 5 | 5 | require github.com/labstack/echo/v4 v4.11.1 | |
| 6 | 6 | ||
| 7 | 7 | require ( | |
| 8 | + | github.com/PuerkitoBio/goquery v1.8.1 // indirect | |
| 9 | + | github.com/andybalholm/cascadia v1.3.1 // indirect | |
| 10 | + | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | require ( | |
| 14 | + | github.com/labstack/echo v3.3.10+incompatible | |
| 8 | 15 | github.com/labstack/gommon v0.4.0 // indirect | |
| 9 | 16 | github.com/mattn/go-colorable v0.1.13 // indirect | |
| 10 | 17 | github.com/mattn/go-isatty v0.0.19 // indirect | |
| @@ -5,6 +5,13 @@ go 1.21.0 | |||||
| 5 | require github.com/labstack/echo/v4 v4.11.1 | 5 | require github.com/labstack/echo/v4 v4.11.1 | ||
| 6 | 6 | ||||
| 7 | require ( | 7 | require ( | ||
| 8 | + | github.com/PuerkitoBio/goquery v1.8.1 // indirect | |||
| 9 | + | github.com/andybalholm/cascadia v1.3.1 // indirect | |||
| 10 | + | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | require ( | |||
| 14 | + | github.com/labstack/echo v3.3.10+incompatible | |||
| 8 | github.com/labstack/gommon v0.4.0 // indirect | 15 | github.com/labstack/gommon v0.4.0 // indirect | ||
| 9 | github.com/mattn/go-colorable v0.1.13 // indirect | 16 | github.com/mattn/go-colorable v0.1.13 // indirect | ||
| 10 | github.com/mattn/go-isatty v0.0.19 // indirect | 17 | github.com/mattn/go-isatty v0.0.19 // indirect | ||
M go.sum +38 -0 generated file: collapsed
M internal/ui/embed.go +3 -9
| @@ -4,20 +4,14 @@ import ( | |||
| 4 | 4 | "embed" | |
| 5 | 5 | ||
| 6 | 6 | "github.com/labstack/echo/v4" | |
| 7 | - | "gogs.bnema.dev/gordon-echo/pkg/utils" | |
| 8 | 7 | ) | |
| 9 | 8 | ||
| 10 | - | // go embed all:public | |
| 9 | + | //go:embed public/* | |
| 11 | 10 | var public embed.FS | |
| 12 | 11 | ||
| 13 | - | // go embed all:templates | |
| 12 | + | //go:embed templates/* | |
| 14 | 13 | var templates embed.FS | |
| 15 | 14 | ||
| 16 | 15 | var PublicFS = echo.MustSubFS(public, "public") | |
| 17 | - | var TemplateFS = echo.MustSubFS(templates, "templates") | |
| 18 | 16 | ||
| 19 | - | // Render renders a template with the specified data and writes to the response. | |
| 20 | - | func Render(c echo.Context, name string, data any) { | |
| 21 | - | r := &utils.Renderer{} | |
| 22 | - | return r.Render(c, name, data) | |
| 23 | - | } | |
| 17 | + | var TemplateFS = echo.MustSubFS(templates, "templates") | |
| @@ -4,20 +4,14 @@ import ( | |||||
| 4 | "embed" | 4 | "embed" | ||
| 5 | 5 | ||||
| 6 | "github.com/labstack/echo/v4" | 6 | "github.com/labstack/echo/v4" | ||
| 7 | - | "gogs.bnema.dev/gordon-echo/pkg/utils" | |||
| 8 | ) | 7 | ) | ||
| 9 | 8 | ||||
| 10 | - | // go embed all:public | 9 | + | //go:embed public/* |
| 11 | var public embed.FS | 10 | var public embed.FS | ||
| 12 | 11 | ||||
| 13 | - | // go embed all:templates | 12 | + | //go:embed templates/* |
| 14 | var templates embed.FS | 13 | var templates embed.FS | ||
| 15 | 14 | ||||
| 16 | var PublicFS = echo.MustSubFS(public, "public") | 15 | var PublicFS = echo.MustSubFS(public, "public") | ||
| 17 | - | var TemplateFS = echo.MustSubFS(templates, "templates") | |||
| 18 | 16 | ||||
| 19 | - | // Render renders a template with the specified data and writes to the response. | 17 | + | var TemplateFS = echo.MustSubFS(templates, "templates") |
| 20 | - | func Render(c echo.Context, name string, data any) { | |||
| 21 | - | r := &utils.Renderer{} | |||
| 22 | - | return r.Render(c, name, data) | |||
| 23 | - | } | |||
A internal/ui/public/index.html +18 -0
| @@ -0,0 +1,18 @@ | |||
| 1 | + | <!DOCTYPE html> | |
| 2 | + | <html lang="en"> | |
| 3 | + | ||
| 4 | + | <head> | |
| 5 | + | <meta charset="UTF-8"> | |
| 6 | + | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| 7 | + | <title>{{.website.title}}</title> | |
| 8 | + | <!-- HTMX min js --> | |
| 9 | + | <script src="libs/htmx.min.js"></script> | |
| 10 | + | <!-- UnoCSS --> | |
| 11 | + | <script src="https://cdn.jsdelivr.net/npm/@unocss/runtime"></script> | |
| 12 | + | ||
| 13 | + | </head> | |
| 14 | + | ||
| 15 | + | <body id="app" hx-get="/htmx" hx-headers='{"X-Fragment": "hello"}' hx-trigger="load"></body> | |
| 16 | + | ||
| 17 | + | ||
| 18 | + | </html>⏎̸ | |
| @@ -0,0 +1,18 @@ | |||||
| 1 | + | <!DOCTYPE html> | |||
| 2 | + | <html lang="en"> | |||
| 3 | + | ||||
| 4 | + | <head> | |||
| 5 | + | <meta charset="UTF-8"> | |||
| 6 | + | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
| 7 | + | <title>{{.website.title}}</title> | |||
| 8 | + | <!-- HTMX min js --> | |||
| 9 | + | <script src="libs/htmx.min.js"></script> | |||
| 10 | + | <!-- UnoCSS --> | |||
| 11 | + | <script src="https://cdn.jsdelivr.net/npm/@unocss/runtime"></script> | |||
| 12 | + | ||||
| 13 | + | </head> | |||
| 14 | + | ||||
| 15 | + | <body id="app" hx-get="/htmx" hx-headers='{"X-Fragment": "hello"}' hx-trigger="load"></body> | |||
| 16 | + | ||||
| 17 | + | ||||
| 18 | + | </html>⏎̸ | |||
R internal/ui/public/libs/htmx.min.js.js → internal/ui/public/libs/htmx.min.js +0 -0 renamed, 100% similar
renamed, 100% similar
A internal/ui/templates/components.html +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | ||
| 2 | + | ||
| 3 | + | ||
| 4 | + | <div id ="hello"> | |
| 5 | + | <h1 class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s">Hello World from components.html</h1> | |
| 6 | + | </div>⏎̸ | |
| @@ -0,0 +1,6 @@ | |||||
| 1 | + | ||||
| 2 | + | ||||
| 3 | + | ||||
| 4 | + | <div id ="hello"> | |||
| 5 | + | <h1 class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s">Hello World from components.html</h1> | |||
| 6 | + | </div>⏎̸ | |||
D internal/ui/templates/index.html +0 -20
| @@ -1,20 +0,0 @@ | |||
| 1 | - | <!DOCTYPE html> | |
| 2 | - | <html lang="en"> | |
| 3 | - | ||
| 4 | - | <head> | |
| 5 | - | <meta charset="UTF-8"> | |
| 6 | - | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| 7 | - | <title>{{website.title}}</title> | |
| 8 | - | <!-- HTMX min js --> | |
| 9 | - | <script src="./libs/htmx.min.js"></script> | |
| 10 | - | <!-- UnoCSS --> | |
| 11 | - | <link rel="stylesheet" href="./libs/uno.css"> | |
| 12 | - | ||
| 13 | - | </head> | |
| 14 | - | ||
| 15 | - | <body id="app"> | |
| 16 | - | <!-- a quick hello world customized by uno css --> | |
| 17 | - | <h1 class="text-center text-4xl text-blue-500">Hello World</h1> | |
| 18 | - | </body> | |
| 19 | - | ||
| 20 | - | </html>⏎̸ | |
| @@ -1,20 +0,0 @@ | |||||
| 1 | - | <!DOCTYPE html> | |||
| 2 | - | <html lang="en"> | |||
| 3 | - | ||||
| 4 | - | <head> | |||
| 5 | - | <meta charset="UTF-8"> | |||
| 6 | - | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||
| 7 | - | <title>{{website.title}}</title> | |||
| 8 | - | <!-- HTMX min js --> | |||
| 9 | - | <script src="./libs/htmx.min.js"></script> | |||
| 10 | - | <!-- UnoCSS --> | |||
| 11 | - | <link rel="stylesheet" href="./libs/uno.css"> | |||
| 12 | - | ||||
| 13 | - | </head> | |||
| 14 | - | ||||
| 15 | - | <body id="app"> | |||
| 16 | - | <!-- a quick hello world customized by uno css --> | |||
| 17 | - | <h1 class="text-center text-4xl text-blue-500">Hello World</h1> | |||
| 18 | - | </body> | |||
| 19 | - | ||||
| 20 | - | </html>⏎̸ | |||
A pkg/utils/fragments.go +34 -0
| @@ -0,0 +1,34 @@ | |||
| 1 | + | package utils | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | ||
| 6 | + | "github.com/PuerkitoBio/goquery" | |
| 7 | + | "gogs.bnema.dev/gordon-echo/internal/ui" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // GetHTMLFragmentByID returns the HTML fragment with the specified id | |
| 11 | + | func GetHTMLFragmentByID(id string) (string, error) { | |
| 12 | + | data, err := ui.TemplateFS.Open("components.html") | |
| 13 | + | if err != nil { | |
| 14 | + | return "", fmt.Errorf("failed to open components.html: %w", err) | |
| 15 | + | } | |
| 16 | + | defer data.Close() | |
| 17 | + | ||
| 18 | + | doc, err := goquery.NewDocumentFromReader(data) | |
| 19 | + | if err != nil { | |
| 20 | + | return "", fmt.Errorf("failed to parse HTML document: %w", err) | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | // Find the div with the specified id | |
| 24 | + | fragmentContent, err := doc.Find("#" + id).Html() | |
| 25 | + | if err != nil { | |
| 26 | + | return "", fmt.Errorf("failed to extract HTML for id %s: %w", id, err) | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | if fragmentContent == "" { | |
| 30 | + | return "", fmt.Errorf("element with id %s not found", id) | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | return fragmentContent, nil | |
| 34 | + | } | |
| @@ -0,0 +1,34 @@ | |||||
| 1 | + | package utils | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | ||||
| 6 | + | "github.com/PuerkitoBio/goquery" | |||
| 7 | + | "gogs.bnema.dev/gordon-echo/internal/ui" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // GetHTMLFragmentByID returns the HTML fragment with the specified id | |||
| 11 | + | func GetHTMLFragmentByID(id string) (string, error) { | |||
| 12 | + | data, err := ui.TemplateFS.Open("components.html") | |||
| 13 | + | if err != nil { | |||
| 14 | + | return "", fmt.Errorf("failed to open components.html: %w", err) | |||
| 15 | + | } | |||
| 16 | + | defer data.Close() | |||
| 17 | + | ||||
| 18 | + | doc, err := goquery.NewDocumentFromReader(data) | |||
| 19 | + | if err != nil { | |||
| 20 | + | return "", fmt.Errorf("failed to parse HTML document: %w", err) | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | // Find the div with the specified id | |||
| 24 | + | fragmentContent, err := doc.Find("#" + id).Html() | |||
| 25 | + | if err != nil { | |||
| 26 | + | return "", fmt.Errorf("failed to extract HTML for id %s: %w", id, err) | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | if fragmentContent == "" { | |||
| 30 | + | return "", fmt.Errorf("element with id %s not found", id) | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | return fragmentContent, nil | |||
| 34 | + | } | |||
M pkg/utils/render.go +6 -6
| @@ -7,24 +7,24 @@ import ( | |||
| 7 | 7 | ) | |
| 8 | 8 | ||
| 9 | 9 | type Renderer struct { | |
| 10 | - | template *template.Template | |
| 11 | - | parseError error | |
| 10 | + | Template *template.Template | |
| 11 | + | ParseError error | |
| 12 | 12 | } | |
| 13 | 13 | ||
| 14 | 14 | // Render executes the template with the specified data as the dot object | |
| 15 | 15 | // and returns the result as plain string. | |
| 16 | 16 | func (r *Renderer) Render(data any) (string, error) { | |
| 17 | - | if r.parseError != nil { | |
| 18 | - | return "", r.parseError | |
| 17 | + | if r.ParseError != nil { | |
| 18 | + | return "", r.ParseError | |
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | - | if r.template == nil { | |
| 21 | + | if r.Template == nil { | |
| 22 | 22 | return "", errors.New("invalid or nil template") | |
| 23 | 23 | } | |
| 24 | 24 | ||
| 25 | 25 | buf := new(bytes.Buffer) | |
| 26 | 26 | ||
| 27 | - | if err := r.template.Execute(buf, data); err != nil { | |
| 27 | + | if err := r.Template.ExecuteTemplate(buf, "index", data); err { | |
| 28 | 28 | return "", err | |
| 29 | 29 | } | |
| 30 | 30 | ||
| @@ -7,24 +7,24 @@ import ( | |||||
| 7 | ) | 7 | ) | ||
| 8 | 8 | ||||
| 9 | type Renderer struct { | 9 | type Renderer struct { | ||
| 10 | - | template *template.Template | 10 | + | Template *template.Template |
| 11 | - | parseError error | 11 | + | ParseError error |
| 12 | } | 12 | } | ||
| 13 | 13 | ||||
| 14 | // Render executes the template with the specified data as the dot object | 14 | // Render executes the template with the specified data as the dot object | ||
| 15 | // and returns the result as plain string. | 15 | // and returns the result as plain string. | ||
| 16 | func (r *Renderer) Render(data any) (string, error) { | 16 | func (r *Renderer) Render(data any) (string, error) { | ||
| 17 | - | if r.parseError != nil { | 17 | + | if r.ParseError != nil { |
| 18 | - | return "", r.parseError | 18 | + | return "", r.ParseError |
| 19 | } | 19 | } | ||
| 20 | 20 | ||||
| 21 | - | if r.template == nil { | 21 | + | if r.Template == nil { |
| 22 | return "", errors.New("invalid or nil template") | 22 | return "", errors.New("invalid or nil template") | ||
| 23 | } | 23 | } | ||
| 24 | 24 | ||||
| 25 | buf := new(bytes.Buffer) | 25 | buf := new(bytes.Buffer) | ||
| 26 | 26 | ||||
| 27 | - | if err := r.template.Execute(buf, data); err != nil { | 27 | + | if err := r.Template.ExecuteTemplate(buf, "index", data); err { |
| 28 | return "", err | 28 | return "", err | ||
| 29 | } | 29 | } | ||
| 30 | 30 | ||||