feat: integrate HTML template engine with theme selection
- Add theme configuration to WebServer struct - Create default theme with base.html layout template - Update content.go to apply theme templates to generated HTML - Output is now full HTML documents with styling Co-Authored-By: Claude (glm-5) <noreply@anthropic.com>
This commit is contained in:
10
PLAN.md
10
PLAN.md
@@ -10,7 +10,7 @@
|
|||||||
## Current Status
|
## Current Status
|
||||||
- [x] Project backbone exists (HTTP server, graceful shutdown, config reading).
|
- [x] Project backbone exists (HTTP server, graceful shutdown, config reading).
|
||||||
- [x] Markdown parsing logic implemented (using goldmark).
|
- [x] Markdown parsing logic implemented (using goldmark).
|
||||||
- [ ] HTML Template engine integrated (Hugo-like theme selection).
|
- [x] HTML Template engine integrated (Hugo-like theme selection).
|
||||||
- [ ] PDF Generation implemented (Pure Go library selected and integrated).
|
- [ ] PDF Generation implemented (Pure Go library selected and integrated).
|
||||||
- [x] CLI Mode (`gocv`) generates static files to `./output` and exits.
|
- [x] CLI Mode (`gocv`) generates static files to `./output` and exits.
|
||||||
- [ ] Serve Mode (`gocv serve`) hosts HTML and serves PDF on demand.
|
- [ ] Serve Mode (`gocv serve`) hosts HTML and serves PDF on demand.
|
||||||
@@ -19,11 +19,12 @@
|
|||||||
|
|
||||||
## Active Task
|
## Active Task
|
||||||
- [x] Analyze existing backbone code and integrate Markdown parsing.
|
- [x] Analyze existing backbone code and integrate Markdown parsing.
|
||||||
- [ ] Integrate HTML template engine with theme selection.
|
- [x] Integrate HTML template engine with theme selection.
|
||||||
|
- [ ] Implement Serve Mode with file watching for live reload.
|
||||||
|
|
||||||
## Known Issues / Blockers
|
## Known Issues / Blockers
|
||||||
- [ ] Identify best Pure Go PDF library that supports HTML/CSS (or define CSS subset).
|
- [ ] Identify best Pure Go PDF library that supports HTML/CSS (or define CSS subset).
|
||||||
- [ ] Current output is raw HTML fragments without full HTML document structure.
|
- [x] Current output is raw HTML fragments without full HTML document structure.
|
||||||
|
|
||||||
## Completed Log
|
## Completed Log
|
||||||
- [x] Initial project structure defined.
|
- [x] Initial project structure defined.
|
||||||
@@ -31,3 +32,6 @@
|
|||||||
- [x] CLI mode vs Serve mode distinction implemented (checks os.Args[1] for "serve").
|
- [x] CLI mode vs Serve mode distinction implemented (checks os.Args[1] for "serve").
|
||||||
- [x] Content reading from `./content` directory implemented.
|
- [x] Content reading from `./content` directory implemented.
|
||||||
- [x] Markdown to HTML conversion using goldmark library.
|
- [x] Markdown to HTML conversion using goldmark library.
|
||||||
|
- [x] Theme system with templates/themes/{theme}/base.html structure.
|
||||||
|
- [x] Config.yaml theme setting (defaults to "default").
|
||||||
|
- [x] Output now generates complete HTML documents with styling.
|
||||||
|
|||||||
28
content.go
28
content.go
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -18,6 +19,12 @@ type ContentFile struct {
|
|||||||
HTML string // Converted HTML
|
HTML string // Converted HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PageData represents data passed to the template
|
||||||
|
type PageData struct {
|
||||||
|
Title string
|
||||||
|
Content template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
// generateOutput reads content from ./content, processes it, and writes to ./output
|
// generateOutput reads content from ./content, processes it, and writes to ./output
|
||||||
func generateOutput() error {
|
func generateOutput() error {
|
||||||
// Ensure content directory exists
|
// Ensure content directory exists
|
||||||
@@ -30,6 +37,13 @@ func generateOutput() error {
|
|||||||
return fmt.Errorf("failed to create output directory: %w", err)
|
return fmt.Errorf("failed to create output directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the theme template
|
||||||
|
themePath := filepath.Join("themes", ws.Theme, "base.html")
|
||||||
|
tmpl, err := template.ParseFiles(themePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load theme template %s: %w", themePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
// Read all markdown files from content directory
|
// Read all markdown files from content directory
|
||||||
files, err := readContentFiles()
|
files, err := readContentFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -52,12 +66,22 @@ func generateOutput() error {
|
|||||||
}
|
}
|
||||||
file.HTML = html
|
file.HTML = html
|
||||||
|
|
||||||
|
// Apply theme template
|
||||||
|
var output bytes.Buffer
|
||||||
|
data := PageData{
|
||||||
|
Title: file.Name + " - " + ws.AppName,
|
||||||
|
Content: template.HTML(file.HTML),
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(&output, data); err != nil {
|
||||||
|
return fmt.Errorf("failed to execute template for %s: %w", file.SourcePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
// Write output
|
// Write output
|
||||||
outputFile := filepath.Join(outputPath, file.Name+".html")
|
outputFile := filepath.Join(outputPath, file.Name+".html")
|
||||||
if err := os.WriteFile(outputFile, []byte(html), 0644); err != nil {
|
if err := os.WriteFile(outputFile, output.Bytes(), 0644); err != nil {
|
||||||
return fmt.Errorf("failed to write %s: %w", outputFile, err)
|
return fmt.Errorf("failed to write %s: %w", outputFile, err)
|
||||||
}
|
}
|
||||||
fmt.Printf(" -> Written: %s\n", outputPath)
|
fmt.Printf(" -> Written: %s\n", outputFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
42
themes/default/base.html
Normal file
42
themes/default/base.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{.Content}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
53
themes/default/baseof.html
Normal file
53
themes/default/baseof.html
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ .Title }}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid #ddd;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 1em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
{{ .Content }}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
type WebServer struct {
|
type WebServer struct {
|
||||||
HTTPServer *http.Server
|
HTTPServer *http.Server
|
||||||
AppName string `yaml:"app_name"`
|
AppName string `yaml:"app_name"`
|
||||||
|
Theme string `yaml:"theme"`
|
||||||
Listen WSListen `yaml:"listen"`
|
Listen WSListen `yaml:"listen"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ func (s *WebServer) Initialize() {
|
|||||||
Port: "80",
|
Port: "80",
|
||||||
}
|
}
|
||||||
s.AppName = "Go Template Container Web Server"
|
s.AppName = "Go Template Container Web Server"
|
||||||
|
s.Theme = "default"
|
||||||
|
s.Theme = "default"
|
||||||
|
|
||||||
// Attempt to read the config file (try both config.yml and config.yaml)
|
// Attempt to read the config file (try both config.yml and config.yaml)
|
||||||
var configFile []byte
|
var configFile []byte
|
||||||
|
|||||||
Reference in New Issue
Block a user