diff --git a/pkg/docgen/generator.go b/pkg/docgen/generator.go index 19ccddb7b..18cafd55f 100644 --- a/pkg/docgen/generator.go +++ b/pkg/docgen/generator.go @@ -17,6 +17,7 @@ package docgen import ( "bytes" _ "embed" + "encoding/json" "fmt" "html" "html/template" @@ -194,7 +195,7 @@ type ( DocumentData struct { Title string - Content string // ProseMirror/Tiptap document JSON; use ProseMirrorJSONToHTML for HTML + Content json.RawMessage // ProseMirror/Tiptap document JSON; use ProseMirrorJSONToHTML for HTML Major int Minor int Classification Classification @@ -319,8 +320,8 @@ const ( // ProseMirrorJSONToHTML converts ProseMirror/Tiptap document JSON to an HTML fragment. // On parse or render failure it returns a single escaped paragraph with the raw input. -func ProseMirrorJSONToHTML(content string) template.HTML { - s := strings.TrimSpace(content) +func ProseMirrorJSONToHTML(content json.RawMessage) template.HTML { + s := strings.TrimSpace(string(content)) if s == "" { return template.HTML("") } diff --git a/pkg/docgen/generator_test.go b/pkg/docgen/generator_test.go index 95095f935..eefa6443f 100644 --- a/pkg/docgen/generator_test.go +++ b/pkg/docgen/generator_test.go @@ -15,6 +15,7 @@ package docgen import ( + "encoding/json" "html/template" "strings" "testing" @@ -37,8 +38,10 @@ func TestRenderHTML(t *testing.T) { { name: "basic document with all fields", data: DocumentData{ - Title: "Test Document", - Content: `{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Main Title"}]},{"type":"paragraph","content":[{"type":"text","text":"This is "},{"type":"text","marks":[{"type":"bold"}],"text":"bold"},{"type":"text","text":" text with "},{"type":"text","marks":[{"type":"italic"}],"text":"italic"},{"type":"text","text":" formatting."}]}]}`, + Title: "Test Document", + Content: json.RawMessage( + []byte(`{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Main Title"}]},{"type":"paragraph","content":[{"type":"text","text":"This is "},{"type":"text","marks":[{"type":"bold"}],"text":"bold"},{"type":"text","text":" text with "},{"type":"text","marks":[{"type":"italic"}],"text":"italic"},{"type":"text","text":" formatting."}]}]}`), + ), Major: 1, Classification: ClassificationPublic, Approvers: []string{"John Doe"}, @@ -67,7 +70,7 @@ func TestRenderHTML(t *testing.T) { name: "document with HTML characters that need escaping", data: DocumentData{ Title: "Test & Doe"}, Signatures: []SignatureData{ { @@ -90,19 +93,21 @@ func TestRenderHTML(t *testing.T) { name: "document with prosemirror content", data: DocumentData{ Title: "ProseMirror Test", - Content: `{"type":"doc","content":[` + - `{"type":"heading","attrs":{"level":2},"content":[{"type":"text","text":"Section 1"}]},` + - `{"type":"bulletList","content":[` + - `{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 1"}]}]},` + - `{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 2"}]}]}` + - `]},` + - `{"type":"paragraph","content":[` + - `{"type":"text","marks":[{"type":"bold"}],"text":"Bold text"},` + - `{"type":"text","text":" and "},` + - `{"type":"text","marks":[{"type":"italic"}],"text":"italic text"}` + - `]},` + - `{"type":"codeBlock","content":[{"type":"text","text":"code block"}]}` + - `]}`, + Content: json.RawMessage([]byte( + `{"type":"doc","content":[` + + `{"type":"heading","attrs":{"level":2},"content":[{"type":"text","text":"Section 1"}]},` + + `{"type":"bulletList","content":[` + + `{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 1"}]}]},` + + `{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 2"}]}]}` + + `]},` + + `{"type":"paragraph","content":[` + + `{"type":"text","marks":[{"type":"bold"}],"text":"Bold text"},` + + `{"type":"text","text":" and "},` + + `{"type":"text","marks":[{"type":"italic"}],"text":"italic text"}` + + `]},` + + `{"type":"codeBlock","content":[{"type":"text","text":"code block"}]}` + + `]}`, + )), }, wantContains: []string{ "
simple text
") - result = ProseMirrorJSONToHTML("**not** json") + result = ProseMirrorJSONToHTML(json.RawMessage([]byte("**not** json"))) assert.Contains(t, string(result), "**not** json
") - result = ProseMirrorJSONToHTML("") + result = ProseMirrorJSONToHTML(nil) assert.Equal(t, template.HTML(""), result) }) } @@ -343,7 +350,7 @@ func TestProseMirrorContentRendering(t *testing.T) { t.Run(tt.name, func(t *testing.T) { data := DocumentData{ Title: "ProseMirror Test", - Content: tt.content, + Content: json.RawMessage([]byte(tt.content)), } result, err := RenderHTML(data) @@ -413,7 +420,7 @@ func TestLargeContent(t *testing.T) { data := DocumentData{ Title: "Large Document", - Content: largeContent.String(), + Content: json.RawMessage([]byte(largeContent.String())), } result, err := RenderHTML(data) @@ -427,20 +434,22 @@ func BenchmarkGenerateHTML(b *testing.B) { data := DocumentData{ Title: "Benchmark Document", - Content: `{"type":"doc","content":[` + - `{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Title"}]},` + - `{"type":"paragraph","content":[` + - `{"type":"text","text":"This is "},` + - `{"type":"text","marks":[{"type":"bold"}],"text":"bold"},` + - `{"type":"text","text":" text with "},` + - `{"type":"text","marks":[{"type":"italic"}],"text":"italic"},` + - `{"type":"text","text":" formatting."}` + - `]},` + - `{"type":"bulletList","content":[` + - `{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 1"}]}]},` + - `{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 2"}]}]}` + - `]}` + - `]}`, + Content: json.RawMessage([]byte( + `{"type":"doc","content":[` + + `{"type":"heading","attrs":{"level":1},"content":[{"type":"text","text":"Title"}]},` + + `{"type":"paragraph","content":[` + + `{"type":"text","text":"This is "},` + + `{"type":"text","marks":[{"type":"bold"}],"text":"bold"},` + + `{"type":"text","text":" text with "},` + + `{"type":"text","marks":[{"type":"italic"}],"text":"italic"},` + + `{"type":"text","text":" formatting."}` + + `]},` + + `{"type":"bulletList","content":[` + + `{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 1"}]}]},` + + `{"type":"listItem","content":[{"type":"paragraph","content":[{"type":"text","text":"Item 2"}]}]}` + + `]}` + + `]}`, + )), Major: 1, Classification: ClassificationPublic, Approvers: []string{"John Doe"}, diff --git a/pkg/probo/document_service.go b/pkg/probo/document_service.go index fa7dfbf3d..41f92d3c5 100644 --- a/pkg/probo/document_service.go +++ b/pkg/probo/document_service.go @@ -1994,7 +1994,7 @@ func exportDocumentPDF( docData := docgen.DocumentData{ Title: version.Title, - Content: version.Content, + Content: json.RawMessage([]byte(version.Content)), Major: version.Major, Minor: version.Minor, Classification: classification, diff --git a/pkg/trust/document_service.go b/pkg/trust/document_service.go index 18ae84d18..24f966eec 100644 --- a/pkg/trust/document_service.go +++ b/pkg/trust/document_service.go @@ -16,10 +16,12 @@ package trust import ( "context" + "encoding/json" "fmt" "io" "errors" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/docgen" @@ -245,7 +247,7 @@ func (s *DocumentService) exportPDFData( docData := docgen.DocumentData{ Title: version.Title, - Content: version.Content, + Content: json.RawMessage([]byte(version.Content)), Major: version.Major, Minor: version.Minor, Classification: classification,