prime8dev

prime8dev
git clone https://git.prime8.dev/prime8dev.git
Log | Files | Refs | README | LICENSE

renderer.go (3597B)


      1 package web
      2 
      3 import (
      4 	"embed"
      5 	"fmt"
      6 	"html/template"
      7 	"io/fs"
      8 	"path"
      9 	"strings"
     10 
     11 	"git.prime8.dev/moncheeta/prime8dev/internal/config"
     12 	"git.prime8.dev/moncheeta/prime8dev/internal/content"
     13 	"git.prime8.dev/moncheeta/prime8dev/internal/external"
     14 	"git.prime8.dev/moncheeta/prime8dev/internal/utils"
     15 )
     16 
     17 //go:embed templates/*.gohtml templates/partials/*.gohtml styles/*.css
     18 var templatesFS embed.FS
     19 
     20 type Data struct {
     21 	Title                string
     22 	Description          string
     23 	ActiveTab            string
     24 	ContentTemplate      string
     25 	About                config.About
     26 	AboutNameASCII       string
     27 	Experience           []config.Experience
     28 	SelectedExperienceID string
     29 	CurrentJob           *config.Experience
     30 	Projects             []config.Project
     31 	SelectedProjectID    string
     32 	Project              *config.Project
     33 	ProjectLinkLabel     string
     34 	ProjectReadme        template.HTML
     35 	Blogs                []content.Blog
     36 	SelectedBlogID       string
     37 	Blog                 *content.Blog
     38 	WakaStats            *external.WakaTimeStats
     39 	Tabs                 []Tab
     40 	SiteURL              string
     41 }
     42 
     43 type Tab struct {
     44 	ID    string
     45 	Label string
     46 	Num   int
     47 }
     48 
     49 type Renderer struct {
     50 	tpl *template.Template
     51 }
     52 
     53 func New() (*Renderer, error) {
     54 	inlineStyles, err := loadInlineStyles()
     55 	if err != nil {
     56 		return nil, err
     57 	}
     58 
     59 	funcs := template.FuncMap{
     60 		"tabPath":         utils.TabPath,
     61 		"tabURL":          utils.TabURL,
     62 		"tagClass":        utils.TagClass,
     63 		"techMeta":        utils.TechMeta,
     64 		"formatMonthYear": utils.FormatMonthYear,
     65 		"formatEndDate":   utils.FormatEndDate,
     66 		"formatDuration":  utils.FormatDuration,
     67 		"durationHasYear": utils.DurationHasYear,
     68 		"timelineYear":    utils.TimelineYear,
     69 		"toHTML":          toHTML,
     70 		"inlineCSS": func(name string) template.CSS {
     71 			if css, ok := inlineStyles[name]; ok {
     72 				return css
     73 			}
     74 			return ""
     75 		},
     76 	}
     77 
     78 	tpl, err := template.New("site").Funcs(funcs).ParseFS(templatesFS, "templates/*.gohtml", "templates/partials/*.gohtml")
     79 	if err != nil {
     80 		return nil, fmt.Errorf("parse templates: %w", err)
     81 	}
     82 
     83 	return &Renderer{tpl: tpl}, nil
     84 }
     85 
     86 func (r *Renderer) Render(name string, data Data) (string, error) {
     87 	var b strings.Builder
     88 	if err := r.tpl.ExecuteTemplate(&b, name, data); err != nil {
     89 		return "", err
     90 	}
     91 	return b.String(), nil
     92 }
     93 
     94 func DefaultTabs() []Tab {
     95 	return []Tab{
     96 		{ID: "about", Label: "About", Num: 1},
     97 		{ID: "projects", Label: "Projects", Num: 2},
     98 		{ID: "experience", Label: "Experience", Num: 3},
     99 		{ID: "blog", Label: "Blog", Num: 4},
    100 	}
    101 }
    102 
    103 func BaseData(title, activeTab string, about config.About) Data {
    104 	return Data{
    105 		Title:           title,
    106 		Description:     fmt.Sprintf("Portfolio and blog of %s", about.Name),
    107 		ActiveTab:       activeTab,
    108 		ContentTemplate: activeTab,
    109 		About:           about,
    110 		AboutNameASCII:  utils.AboutNameASCII(about.Name),
    111 		Tabs:            DefaultTabs(),
    112 		SiteURL:         config.SiteURL,
    113 	}
    114 }
    115 
    116 func toHTML(s string) template.HTML {
    117 	return template.HTML(s)
    118 }
    119 
    120 func loadInlineStyles() (map[string]template.CSS, error) {
    121 	entries, err := fs.ReadDir(templatesFS, "styles")
    122 	if err != nil {
    123 		return nil, fmt.Errorf("read styles: %w", err)
    124 	}
    125 
    126 	styles := make(map[string]template.CSS, len(entries))
    127 	for _, entry := range entries {
    128 		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".css") {
    129 			continue
    130 		}
    131 
    132 		stylePath := path.Join("styles", entry.Name())
    133 		content, err := templatesFS.ReadFile(stylePath)
    134 		if err != nil {
    135 			return nil, fmt.Errorf("read style %s: %w", entry.Name(), err)
    136 		}
    137 
    138 		styles[entry.Name()] = template.CSS(string(content))
    139 	}
    140 
    141 	return styles, nil
    142 }