a90d923956
Previously, HTML in short descriptions was being escaped and displayed as raw text instead of rendering properly. This happened because the safeHTML template function had been removed for security reasons. Changes: - Added safeHTML function back to template.FuncMap (template.go:53-55) - Updated three template locations to use safeHTML pipe: * Experience descriptions (cv-content.html:122) * Award descriptions (cv-content.html:180) * Project descriptions (cv-content.html:232) Security note: The safeHTML function is safe to use here because CV data comes from trusted YAML files controlled by the site owner, not user input. Clear documentation added to prevent misuse with untrusted content. Examples now rendering correctly: - Award: "Premio por excelencia en marketing B2B...con <a href=...>Clicplan</a>" - Projects: Links to Lidering, Jorpack, Delivery Bikes BCN, Mobbeel
106 lines
2.5 KiB
Go
106 lines
2.5 KiB
Go
package templates
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"github.com/juanatsap/cv-site/internal/config"
|
|
)
|
|
|
|
// Manager handles template parsing and rendering
|
|
type Manager struct {
|
|
templates *template.Template
|
|
config *config.TemplateConfig
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewManager creates a new template manager
|
|
func NewManager(cfg *config.TemplateConfig) (*Manager, error) {
|
|
m := &Manager{
|
|
config: cfg,
|
|
}
|
|
|
|
if err := m.loadTemplates(); err != nil {
|
|
return nil, fmt.Errorf("failed to load templates: %w", err)
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
// loadTemplates parses all templates from the configured directory
|
|
func (m *Manager) loadTemplates() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
// Create template with custom functions
|
|
funcMap := template.FuncMap{
|
|
"iterate": func(count int) []int {
|
|
var result []int
|
|
for i := 0; i < count; i++ {
|
|
result = append(result, i)
|
|
}
|
|
return result
|
|
},
|
|
"eq": func(a, b string) bool {
|
|
return a == b
|
|
},
|
|
// safeHTML marks string as safe HTML to prevent escaping
|
|
// SECURITY NOTE: Only use with trusted content from CV YAML files
|
|
// Never use with user-generated content to prevent XSS attacks
|
|
"safeHTML": func(s string) template.HTML {
|
|
return template.HTML(s)
|
|
},
|
|
}
|
|
|
|
// Parse main templates
|
|
pattern := filepath.Join(m.config.Dir, "*.html")
|
|
tmpl, err := template.New("").Funcs(funcMap).ParseGlob(pattern)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing templates from %s: %w", pattern, err)
|
|
}
|
|
|
|
// Try to parse partials if they exist
|
|
partialsPattern := filepath.Join(m.config.PartialsDir, "*.html")
|
|
partials, _ := filepath.Glob(partialsPattern)
|
|
if len(partials) > 0 {
|
|
tmpl, err = tmpl.ParseGlob(partialsPattern)
|
|
if err != nil {
|
|
log.Printf("Warning: error parsing partials: %v", err)
|
|
}
|
|
}
|
|
|
|
m.templates = tmpl
|
|
log.Printf("✓ Templates loaded successfully from %s", m.config.Dir)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reload reloads all templates (useful for hot-reload in development)
|
|
func (m *Manager) Reload() error {
|
|
return m.loadTemplates()
|
|
}
|
|
|
|
// Render executes a template with the given data
|
|
func (m *Manager) Render(name string) (*template.Template, error) {
|
|
// Hot reload in development mode
|
|
if m.config.HotReload {
|
|
if err := m.Reload(); err != nil {
|
|
log.Printf("Warning: template reload failed: %v", err)
|
|
// Continue with cached templates
|
|
}
|
|
}
|
|
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
tmpl := m.templates.Lookup(name)
|
|
if tmpl == nil {
|
|
return nil, fmt.Errorf("template %q not found", name)
|
|
}
|
|
|
|
return tmpl, nil
|
|
}
|