From 85920b8c7a2696115d1f77c046f48f6f00d639f1 Mon Sep 17 00:00:00 2001 From: ivar Date: Tue, 7 Apr 2026 00:23:24 +0200 Subject: Init --- internal/builder/editorjs.go | 255 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 internal/builder/editorjs.go (limited to 'internal/builder') diff --git a/internal/builder/editorjs.go b/internal/builder/editorjs.go new file mode 100644 index 0000000..68237d0 --- /dev/null +++ b/internal/builder/editorjs.go @@ -0,0 +1,255 @@ +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 +} -- cgit v1.3%s
\n", template.HTML(d.Text)) + if d.Caption != "" { + fmt.Fprintf(&buf, "%s\n", template.HTML(d.Caption)) + } + buf.WriteString("