package render import ( "fmt" "html/template" "io/fs" "net/http" "os" "path/filepath" "sync" "github.com/russross/blackfriday/v2" ) type TemplateData struct { Name string FileNameSet []string } type TemplateManager struct { templates map[string]*template.Template mu sync.RWMutex basePath string devMode bool fs fs.FS } func NewTemplateManager(basePath string) *TemplateManager { return NewTemplateManagerFS(os.DirFS("."), basePath) } func NewTemplateManagerFS(fsys fs.FS, basePath string) *TemplateManager { return &TemplateManager{ templates: make(map[string]*template.Template), basePath: basePath, devMode: false, fs: fsys, } } func (tm *TemplateManager) buildTemplatePath(name string) string { return filepath.Join(tm.basePath, name+".tmpl") } func (tm *TemplateManager) GetTemplate(td *TemplateData) (*template.Template, error) { // Skip cache in dev mode if !tm.devMode { tm.mu.RLock() if tmpl, exists := tm.templates[td.Name]; exists { tm.mu.RUnlock() return tmpl, nil } tm.mu.RUnlock() } // Build file paths var files []string for _, file := range td.FileNameSet { files = append(files, tm.buildTemplatePath(file)) } // Parse template tmpl, err := template.ParseFS(tm.fs, files...) if err != nil { return nil, fmt.Errorf("parse template %s: %w", td.Name, err) } // Cache it (unless in dev mode) if !tm.devMode { tm.mu.Lock() tm.templates[td.Name] = tmpl tm.mu.Unlock() } return tmpl, nil } func (tm *TemplateManager) Render(w http.ResponseWriter, name string, data any) error { // Build the template files - include all necessary templates var files []string // Always include base template files = append(files, tm.buildTemplatePath("base")) // Include noteList template (used by index) files = append(files, tm.buildTemplatePath("noteList")) // Include metadata template (used by index) files = append(files, tm.buildTemplatePath("metadata")) // Include metadata template files = append(files, tm.buildTemplatePath("metadata")) // Add the specific template files = append(files, tm.buildTemplatePath(name)) // Parse templates tmpl, err := template.ParseFS(tm.fs, files...) if err != nil { return fmt.Errorf("parse template %s: %w", name, err) } // Set content type w.Header().Set("Content-Type", "text/html; charset=utf-8") err = tmpl.ExecuteTemplate(w, "base", data) if err != nil { return fmt.Errorf("execute template %s: %w", name, err) } return nil } // Render markdown to HTML with target="_blank" on links func RenderMarkdown(content []byte) (template.HTML, error) { renderer := blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{ Flags: blackfriday.CommonHTMLFlags | blackfriday.HrefTargetBlank, }) html := blackfriday.Run(content, blackfriday.WithRenderer(renderer)) return template.HTML(html), nil }