+107
-25
appview/pages/pages.go
+107
-25
appview/pages/pages.go
···
11
"io/fs"
12
"log"
13
"net/http"
14
"path"
15
"path/filepath"
16
"slices"
···
35
var Files embed.FS
36
37
type Pages struct {
38
-
t map[string]*template.Template
39
}
40
41
-
func NewPages() *Pages {
42
-
templates := make(map[string]*template.Template)
43
44
var fragmentPaths []string
45
// First, collect all fragment paths
46
-
err := fs.WalkDir(Files, "templates", func(path string, d fs.DirEntry, err error) error {
47
if err != nil {
48
return err
49
}
50
-
51
if d.IsDir() {
52
return nil
53
}
54
-
55
if !strings.HasSuffix(path, ".html") {
56
return nil
57
}
58
-
59
if !strings.Contains(path, "fragments/") {
60
return nil
61
}
62
-
63
name := strings.TrimPrefix(path, "templates/")
64
name = strings.TrimSuffix(name, ".html")
65
-
66
tmpl, err := template.New(name).
67
Funcs(funcMap()).
68
-
ParseFS(Files, path)
69
if err != nil {
70
log.Fatalf("setting up fragment: %v", err)
71
}
72
-
73
templates[name] = tmpl
74
fragmentPaths = append(fragmentPaths, path)
75
log.Printf("loaded fragment: %s", name)
···
80
}
81
82
// Then walk through and setup the rest of the templates
83
-
err = fs.WalkDir(Files, "templates", func(path string, d fs.DirEntry, err error) error {
84
if err != nil {
85
return err
86
}
87
-
88
if d.IsDir() {
89
return nil
90
}
91
-
92
if !strings.HasSuffix(path, "html") {
93
return nil
94
}
95
-
96
// Skip fragments as they've already been loaded
97
if strings.Contains(path, "fragments/") {
98
return nil
99
}
100
-
101
// Skip layouts
102
if strings.Contains(path, "layouts/") {
103
return nil
104
}
105
-
106
name := strings.TrimPrefix(path, "templates/")
107
name = strings.TrimSuffix(name, ".html")
108
-
109
// Add the page template on top of the base
110
allPaths := []string{}
111
allPaths = append(allPaths, "templates/layouts/*.html")
···
113
allPaths = append(allPaths, path)
114
tmpl, err := template.New(name).
115
Funcs(funcMap()).
116
-
ParseFS(Files, allPaths...)
117
if err != nil {
118
return fmt.Errorf("setting up template: %w", err)
119
}
120
-
121
templates[name] = tmpl
122
log.Printf("loaded template: %s", name)
123
return nil
···
127
}
128
129
log.Printf("total templates loaded: %d", len(templates))
130
131
-
return &Pages{
132
-
t: templates,
133
}
134
-
}
135
136
-
type LoginParams struct {
137
}
138
139
func (p *Pages) execute(name string, w io.Writer, params any) error {
140
-
return p.t[name].ExecuteTemplate(w, "layouts/base", params)
141
}
142
143
func (p *Pages) executePlain(name string, w io.Writer, params any) error {
···
146
147
func (p *Pages) executeRepo(name string, w io.Writer, params any) error {
148
return p.t[name].ExecuteTemplate(w, "layouts/repobase", params)
149
}
150
151
func (p *Pages) Login(w io.Writer, params LoginParams) error {
···
794
}
795
796
func (p *Pages) Static() http.Handler {
797
sub, err := fs.Sub(Files, "static")
798
if err != nil {
799
log.Fatalf("no static dir found? that's crazy: %v", err)
···
11
"io/fs"
12
"log"
13
"net/http"
14
+
"os"
15
"path"
16
"path/filepath"
17
"slices"
···
36
var Files embed.FS
37
38
type Pages struct {
39
+
t map[string]*template.Template
40
+
dev bool
41
+
embedFS embed.FS
42
+
templateDir string // Path to templates on disk for dev mode
43
}
44
45
+
func NewPages(dev bool) *Pages {
46
+
p := &Pages{
47
+
t: make(map[string]*template.Template),
48
+
dev: dev,
49
+
embedFS: Files,
50
+
templateDir: "appview/pages",
51
+
}
52
53
+
// Initial load of all templates
54
+
p.loadAllTemplates()
55
+
56
+
return p
57
+
}
58
+
59
+
func (p *Pages) loadAllTemplates() {
60
+
templates := make(map[string]*template.Template)
61
var fragmentPaths []string
62
+
63
+
// Use embedded FS for initial loading
64
// First, collect all fragment paths
65
+
err := fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error {
66
if err != nil {
67
return err
68
}
69
if d.IsDir() {
70
return nil
71
}
72
if !strings.HasSuffix(path, ".html") {
73
return nil
74
}
75
if !strings.Contains(path, "fragments/") {
76
return nil
77
}
78
name := strings.TrimPrefix(path, "templates/")
79
name = strings.TrimSuffix(name, ".html")
80
tmpl, err := template.New(name).
81
Funcs(funcMap()).
82
+
ParseFS(p.embedFS, path)
83
if err != nil {
84
log.Fatalf("setting up fragment: %v", err)
85
}
86
templates[name] = tmpl
87
fragmentPaths = append(fragmentPaths, path)
88
log.Printf("loaded fragment: %s", name)
···
93
}
94
95
// Then walk through and setup the rest of the templates
96
+
err = fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error {
97
if err != nil {
98
return err
99
}
100
if d.IsDir() {
101
return nil
102
}
103
if !strings.HasSuffix(path, "html") {
104
return nil
105
}
106
// Skip fragments as they've already been loaded
107
if strings.Contains(path, "fragments/") {
108
return nil
109
}
110
// Skip layouts
111
if strings.Contains(path, "layouts/") {
112
return nil
113
}
114
name := strings.TrimPrefix(path, "templates/")
115
name = strings.TrimSuffix(name, ".html")
116
// Add the page template on top of the base
117
allPaths := []string{}
118
allPaths = append(allPaths, "templates/layouts/*.html")
···
120
allPaths = append(allPaths, path)
121
tmpl, err := template.New(name).
122
Funcs(funcMap()).
123
+
ParseFS(p.embedFS, allPaths...)
124
if err != nil {
125
return fmt.Errorf("setting up template: %w", err)
126
}
127
templates[name] = tmpl
128
log.Printf("loaded template: %s", name)
129
return nil
···
133
}
134
135
log.Printf("total templates loaded: %d", len(templates))
136
+
p.t = templates
137
+
}
138
139
+
// loadTemplateFromDisk loads a template from the filesystem in dev mode
140
+
func (p *Pages) loadTemplateFromDisk(name string) error {
141
+
if !p.dev {
142
+
return nil
143
}
144
145
+
log.Printf("reloading template from disk: %s", name)
146
+
147
+
// Find all fragments first
148
+
var fragmentPaths []string
149
+
err := filepath.WalkDir(filepath.Join(p.templateDir, "templates"), func(path string, d fs.DirEntry, err error) error {
150
+
if err != nil {
151
+
return err
152
+
}
153
+
if d.IsDir() {
154
+
return nil
155
+
}
156
+
if !strings.HasSuffix(path, ".html") {
157
+
return nil
158
+
}
159
+
if !strings.Contains(path, "fragments/") {
160
+
return nil
161
+
}
162
+
fragmentPaths = append(fragmentPaths, path)
163
+
return nil
164
+
})
165
+
if err != nil {
166
+
return fmt.Errorf("walking disk template dir for fragments: %w", err)
167
+
}
168
+
169
+
// Find the template path on disk
170
+
templatePath := filepath.Join(p.templateDir, "templates", name+".html")
171
+
if _, err := os.Stat(templatePath); os.IsNotExist(err) {
172
+
return fmt.Errorf("template not found on disk: %s", name)
173
+
}
174
+
175
+
// Create a new template
176
+
tmpl := template.New(name).Funcs(funcMap())
177
+
178
+
// Parse layouts
179
+
layoutGlob := filepath.Join(p.templateDir, "templates", "layouts", "*.html")
180
+
layouts, err := filepath.Glob(layoutGlob)
181
+
if err != nil {
182
+
return fmt.Errorf("finding layout templates: %w", err)
183
+
}
184
+
185
+
// Create paths for parsing
186
+
allFiles := append(layouts, fragmentPaths...)
187
+
allFiles = append(allFiles, templatePath)
188
+
189
+
// Parse all templates
190
+
tmpl, err = tmpl.ParseFiles(allFiles...)
191
+
if err != nil {
192
+
return fmt.Errorf("parsing template files: %w", err)
193
+
}
194
+
195
+
// Update the template in the map
196
+
p.t[name] = tmpl
197
+
log.Printf("template reloaded from disk: %s", name)
198
+
return nil
199
}
200
201
func (p *Pages) execute(name string, w io.Writer, params any) error {
202
+
// In dev mode, reload the template from disk before executing
203
+
if p.dev {
204
+
if err := p.loadTemplateFromDisk(name); err != nil {
205
+
log.Printf("warning: failed to reload template %s from disk: %v", name, err)
206
+
// Continue with the existing template
207
+
}
208
+
}
209
+
210
+
tmpl, exists := p.t[name]
211
+
if !exists {
212
+
return fmt.Errorf("template not found: %s", name)
213
+
}
214
+
215
+
return tmpl.ExecuteTemplate(w, "layouts/base", params)
216
}
217
218
func (p *Pages) executePlain(name string, w io.Writer, params any) error {
···
221
222
func (p *Pages) executeRepo(name string, w io.Writer, params any) error {
223
return p.t[name].ExecuteTemplate(w, "layouts/repobase", params)
224
+
}
225
+
226
+
type LoginParams struct {
227
}
228
229
func (p *Pages) Login(w io.Writer, params LoginParams) error {
···
872
}
873
874
func (p *Pages) Static() http.Handler {
875
+
if p.dev {
876
+
return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static")))
877
+
}
878
+
879
sub, err := fs.Sub(Files, "static")
880
if err != nil {
881
log.Fatalf("no static dir found? that's crazy: %v", err)
+1
-1
appview/state/state.go
+1
-1
appview/state/state.go
+10
-1
flake.nix
+10
-1
flake.nix
···
173
${pkgs.air}/bin/air -c /dev/null \
174
-build.cmd "${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o ./appview/pages/static/tw.css && ${pkgs.go}/bin/go build -o ./out/${name}.out ./cmd/${name}/main.go" \
175
-build.bin "./out/${name}.out" \
176
-
-build.include_ext "go,html,css"
177
'';
178
in {
179
watch-appview = {
···
183
watch-knotserver = {
184
type = "app";
185
program = ''${air-watcher "knotserver"}/bin/run'';
186
};
187
});
188
···
173
${pkgs.air}/bin/air -c /dev/null \
174
-build.cmd "${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o ./appview/pages/static/tw.css && ${pkgs.go}/bin/go build -o ./out/${name}.out ./cmd/${name}/main.go" \
175
-build.bin "./out/${name}.out" \
176
+
-build.include_ext "go"
177
+
'';
178
+
tailwind-watcher =
179
+
pkgs.writeShellScriptBin "run"
180
+
''
181
+
${pkgs.tailwindcss}/bin/tailwindcss -w -i input.css -o ./appview/pages/static/tw.css
182
'';
183
in {
184
watch-appview = {
···
188
watch-knotserver = {
189
type = "app";
190
program = ''${air-watcher "knotserver"}/bin/run'';
191
+
};
192
+
watch-tailwind = {
193
+
type = "app";
194
+
program = ''${tailwind-watcher}/bin/run'';
195
};
196
});
197