From a6355e7a6530af3335c4cd8af05f1e9c8b978169 Mon Sep 17 00:00:00 2001 From: ivar Date: Sat, 4 Apr 2026 16:34:46 +0200 Subject: . --- internal/builder/editorjs.go | 247 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 internal/builder/editorjs.go (limited to 'internal/builder/editorjs.go') diff --git a/internal/builder/editorjs.go b/internal/builder/editorjs.go new file mode 100644 index 0000000..4db4d84 --- /dev/null +++ b/internal/builder/editorjs.go @@ -0,0 +1,247 @@ +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: + // Silently ignore unknown block types + 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 + } + // EditorJS stores inline HTML; wrap in template.HTML to avoid re-escaping + html := fmt.Sprintf("
%s
\n", template.HTML(d.Text)) + return html, 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 + } + html := fmt.Sprintf("%s\n", template.HTMLEscapeString(d.Code))
+ return html, 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 + } + // script blocks don't render inline HTML; return the URL for the template to inject + 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)) + script := "/assets/components/" + d.Name + ".js" + return buf.String(), []string{script}, nil +} -- cgit v1.3%s
\n", template.HTML(d.Text)) + if d.Caption != "" { + fmt.Fprintf(&buf, "%s\n", template.HTML(d.Caption)) + } + buf.WriteString("