Files
KenanZhu fec3ace600
Release / Validate Templates (push) Canceled after 0s
Release / Package & Release (push) Canceled after 0s
Release / Update Latest Release Documentation (push) Canceled after 0s
chore: migrate mail themes to shared framework and locked upstream inputs
2026-10-09 18:34:36 +08:00

115 lines
2.8 KiB
Go

package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
// TemplateConfig holds metadata for a single mail template type.
type TemplateConfig struct {
Name string `json:"name"`
Desc string `json:"desc"`
Path []string `json:"-"` // Discovered from the pinned upstream snapshot.
Params map[string]any `json:"params"`
}
// TemplatesConfig is the root structure of templates_config.json.
type TemplatesConfig struct {
Templates map[string]TemplateConfig `json:"templates"`
}
// Load reads and parses a templates_config.json file.
func Load(path string) (*TemplatesConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("cannot read config file %s: %w", path, err)
}
var cfg TemplatesConfig
decoder := json.NewDecoder(strings.NewReader(string(data)))
decoder.UseNumber()
if err := decoder.Decode(&cfg); err != nil {
return nil, fmt.Errorf("cannot parse config file %s: %w", path, err)
}
if len(cfg.Templates) == 0 {
return nil, fmt.Errorf("config file %s contains no template definitions", path)
}
for id, t := range cfg.Templates {
t.Params = normalizeNumbers(t.Params).(map[string]any)
cfg.Templates[id] = t
}
return &cfg, nil
}
func normalizeNumbers(v any) any {
switch v := v.(type) {
case json.Number:
if n, err := v.Int64(); err == nil {
return n
}
n, _ := v.Float64()
return n
case map[string]any:
for k, value := range v {
v[k] = normalizeNumbers(value)
}
return v
case []any:
for i, value := range v {
v[i] = normalizeNumbers(value)
}
return v
default:
return v
}
}
// PathStr returns the file path of a template definition joined with OS separator.
func (t *TemplateConfig) PathStr() string {
return filepath.Join(t.Path...)
}
// Registry builds a lookup map suitable for serializing into rendered.js
// as window.__REGISTRY__: maps template ID → {name, desc, path}.
func Registry(cfg *TemplatesConfig) map[string]map[string]string {
reg := make(map[string]map[string]string, len(cfg.Templates))
for id, t := range cfg.Templates {
reg[id] = map[string]string{
"name": t.Name,
"desc": t.Desc,
"path": strings.Join(t.Path, "/"),
}
}
return reg
}
// FlattenParams converts nested template params into a flat dot-notation map
// suitable for the preview panel display in rendered.js.
func FlattenParams(params map[string]any) map[string]string {
flat := make(map[string]string)
flatten("", params, flat)
return flat
}
func flatten(prefix string, val any, out map[string]string) {
switch v := val.(type) {
case map[string]any:
for k, inner := range v {
key := k
if prefix != "" {
key = prefix + "." + k
}
flatten(key, inner, out)
}
case []any:
out[prefix] = fmt.Sprintf("[%d items]", len(v))
default:
out[prefix] = fmt.Sprintf("%v", v)
}
}