chore: migrate mail themes to shared framework and locked upstream inputs
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
Release / Update Latest Release Documentation (push) Canceled after 0s

This commit is contained in:
KenanZhu committed 2026-10-09 18:34:36 +08:00
1 parent 5c0589f6f6
commit fec3ace600
225 files changed
+4718 -15807

No files matched your search

+39 -12
View File
@@ -22,7 +22,7 @@ type DevServerConfig struct {
}
// DevServer is a live-reload dev server with SSE-based browser notifications.
// It serves preview/ as static files, watches themes/ for .tmpl changes,
// It serves preview/ as static files, watches theme resources and snapshots,
// re-renders templates in-process, and pushes reload events to connected browsers.
type DevServer struct {
cfg DevServerConfig
@@ -46,7 +46,7 @@ func NewDevServer(cfg DevServerConfig) *DevServer {
func (s *DevServer) Start() error {
// Build initial rendered.js
if err := s.rebuildAll(); err != nil {
fmt.Fprintf(os.Stderr, "\033[33m[W]\033[0m [Builder] Initial render warning: %v\n", err)
return fmt.Errorf("initial preview build: %w", err)
}
// Start file watcher in background
@@ -61,11 +61,11 @@ func (s *DevServer) Start() error {
previewFS := http.FileServer(http.Dir(s.cfg.PreviewDir))
mux.Handle("/", previewFS)
addr := fmt.Sprintf(":%d", s.cfg.Port)
addr := fmt.Sprintf("127.0.0.1:%d", s.cfg.Port)
fmt.Printf("\033[32m[I]\033[0m [Server] Gitea Mail Templates — Dev Server\n")
fmt.Printf("\033[32m[I]\033[0m [Server] http://localhost%s\n", addr)
fmt.Printf("\033[32m[I]\033[0m [Watcher] Watching themes/ for changes\n")
fmt.Printf("\033[32m[I]\033[0m [Server] SSE endpoint ws://localhost%s/events\n\n", addr)
fmt.Printf("\033[32m[I]\033[0m [Server] http://%s\n", addr)
fmt.Printf("\033[32m[I]\033[0m [Watcher] Watching theme CSS, framework, lock/cache and fixtures\n")
fmt.Printf("\033[32m[I]\033[0m [Server] SSE endpoint http://%s/events\n\n", addr)
return http.ListenAndServe(addr, mux)
}
@@ -131,19 +131,42 @@ func (s *DevServer) broadcast(typ string, payload map[string]interface{}) {
// --- File watcher (polling) ---
// scanMods scans themes/ for .tmpl files and returns a map of path → mod time.
// scanMods records theme resources, snapshot files and fixture modification times.
func (s *DevServer) scanMods() (map[string]time.Time, error) {
mods := make(map[string]time.Time)
err := filepath.Walk(s.cfg.ThemesDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil // skip unreadable
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ".tmpl") {
if !info.IsDir() && (strings.HasSuffix(info.Name(), ".json") || strings.HasSuffix(info.Name(), ".css") || strings.HasSuffix(info.Name(), ".html")) {
mods[path] = info.ModTime()
}
return nil
})
return mods, err
if err != nil {
return mods, err
}
for _, dir := range []string{filepath.Join(filepath.Dir(s.cfg.ThemesDir), "framework"), filepath.Join(filepath.Dir(s.cfg.ThemesDir), "build", "upstream"), filepath.Join(filepath.Dir(s.cfg.ThemesDir), "tools", "data")} {
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
mods[path] = info.ModTime()
}
return nil
})
if err != nil {
return mods, err
}
}
lock := filepath.Join(filepath.Dir(s.cfg.ThemesDir), "gitea.lock.json")
if info, err := os.Stat(lock); err == nil {
mods[lock] = info.ModTime()
} else {
return mods, err
}
return mods, nil
}
// watchThemes polls the themes directory every 500ms and triggers rebuild on changes.
@@ -213,9 +236,13 @@ func (s *DevServer) rebuildAll() error {
start := time.Now()
fmt.Printf("\033[32m[I]\033[0m [Builder] Rebuilding all themes...\n")
result := RenderAll(s.cfg.ThemesDir, s.cfg.Config, nil)
if result == nil {
err := fmt.Errorf("no results produced")
cfg, err := config.Load(filepath.Join(filepath.Dir(s.cfg.ThemesDir), "tools", "data", "templates_config.json"))
if err != nil {
s.broadcast("error", map[string]interface{}{"message": err.Error()})
return err
}
result, err := RenderAll(s.cfg.ThemesDir, cfg, nil)
if err != nil {
s.broadcast("error", map[string]interface{}{"message": err.Error()})
return err
}