package builder import ( "encoding/json" "fmt" "html/template" "strings" ) // PageData is passed to HTML templates when rendering posts. type PageData struct { Title string Content template.HTML ComponentScripts []string Date string Tags []string Path string } type EditorDocument struct { Version string `json:"version"` Time int64 `json:"time"` Blocks []EditorBlock `json:"blocks"` } type EditorBlock struct { Type string `json:"type"` Data json.RawMessage `json:"data"` } // RenderEditorJS converts EditorJS block JSON to HTML and extracts script URLs. // Returns the rendered HTML body, script URLs, and any error. func RenderEditorJS(blocksJSON string) (html string, scripts []string, err error) { if blocksJSON == "" || blocksJSON == "[]" { return "", nil, nil } var doc EditorDocument if err := json.Unmarshal([]byte(blocksJSON), &doc); err != nil { return "", nil, err } var buf strings.Builder var scriptURLs []string for _, block := range doc.Blocks { blockHTML, blockScripts, err := renderBlock(block) if err != nil { return "", nil, err } if blockHTML != "" { buf.WriteString(blockHTML) } scriptURLs = append(scriptURLs, blockScripts...) } return buf.String(), scriptURLs, nil } func renderBlock(block EditorBlock) (html string, scripts []string, err error) { switch block.Type { case "paragraph": return renderParagraph(block.Data) case "header": return renderHeader(block.Data) case "image": return renderImage(block.Data) case "list": return renderList(block.Data) case "code": return renderCode(block.Data) case "quote": return renderQuote(block.Data) case "script": return renderScript(block.Data) case "component": return renderComponent(block.Data) default: return "", nil, nil } } func renderParagraph(data json.RawMessage) (string, []string, error) { var d struct { Text string `json:"text"` } if err := json.Unmarshal(data, &d); err != nil { return "", nil, err } if strings.TrimSpace(d.Text) == "" { return "", nil, nil } return fmt.Sprintf("
%s
\n", template.HTML(d.Text)), nil, nil } func renderHeader(data json.RawMessage) (string, []string, error) { var d struct { Text string `json:"text"` Level int `json:"level"` } if err := json.Unmarshal(data, &d); err != nil { return "", nil, err } if d.Level < 1 || d.Level > 6 { d.Level = 2 } if strings.TrimSpace(d.Text) == "" { return "", nil, nil } return fmt.Sprintf("%s\n", template.HTMLEscapeString(d.Code)), nil, nil
}
func renderQuote(data json.RawMessage) (string, []string, error) {
var d struct {
Text string `json:"text"`
Caption string `json:"caption"`
}
if err := json.Unmarshal(data, &d); err != nil {
return "", nil, err
}
if strings.TrimSpace(d.Text) == "" {
return "", nil, nil
}
var buf strings.Builder
fmt.Fprintf(&buf, "\n\n") return buf.String(), nil, nil } func renderScript(data json.RawMessage) (string, []string, error) { var d struct { Src string `json:"src"` } if err := json.Unmarshal(data, &d); err != nil { return "", nil, err } if d.Src == "" { return "", nil, nil } return "", []string{d.Src}, nil } func renderComponent(data json.RawMessage) (string, []string, error) { var d struct { Name string `json:"name"` Props map[string]string `json:"props"` } if err := json.Unmarshal(data, &d); err != nil { return "", nil, err } if d.Name == "" { return "", nil, nil } var buf strings.Builder fmt.Fprintf(&buf, "<%s", template.HTMLEscapeString(d.Name)) for k, v := range d.Props { fmt.Fprintf(&buf, " %s=\"%s\"", template.HTMLEscapeString(k), template.HTMLEscapeString(v)) } fmt.Fprintf(&buf, ">%s>\n", template.HTMLEscapeString(d.Name)) return buf.String(), []string{"/assets/components/" + d.Name + ".js"}, nil }%s
\n", template.HTML(d.Text)) if d.Caption != "" { fmt.Fprintf(&buf, "%s\n", template.HTML(d.Caption)) } buf.WriteString("