diff --git a/pkg/help/generate.go b/pkg/help/generate.go index 6f0fa6c..4f20620 100644 --- a/pkg/help/generate.go +++ b/pkg/help/generate.go @@ -34,22 +34,19 @@ func Generate(catalog *Catalog, outputDir string) error { } // 1. index.html - if err := writeStaticPage(outputDir, "index.html", "index.html", indexData{ - Topics: topics, - Groups: groupTopicsByTag(topics), - }); err != nil { + if err := writeFile(outputDir, "index.html", RenderIndexPage(topics)); err != nil { return err } // 2. topics/{id}.html -- one per topic for _, t := range topics { - if err := writeStaticPage(topicsDir, t.ID+".html", "topic.html", topicData{Topic: t}); err != nil { + if err := writeFile(topicsDir, t.ID+".html", RenderTopicPage(t, topics)); err != nil { return err } } // 3. search.html -- client-side search page - if err := writeSearchPage(outputDir); err != nil { + if err := writeFile(outputDir, "search.html", RenderSearchPage("", nil)+clientSearchScript); err != nil { return err } @@ -59,23 +56,17 @@ func Generate(catalog *Catalog, outputDir string) error { } // 5. 404.html - if err := writeStaticPage(outputDir, "404.html", "404.html", nil); err != nil { + if err := writeFile(outputDir, "404.html", Render404Page()); err != nil { return err } return nil } -// writeStaticPage renders a template page to a file. -func writeStaticPage(dir, filename, templatePage string, data any) error { +// writeFile writes content to a file in the given directory. +func writeFile(dir, filename, content string) error { path := filepath.Join(dir, filename) - f, err := os.Create(path) - if err != nil { - return err - } - defer f.Close() - - return renderPage(f, templatePage, data) + return os.WriteFile(path, []byte(content), 0o644) } // writeSearchIndex writes the JSON search index for client-side search. @@ -108,28 +99,6 @@ func writeSearchIndex(outputDir string, topics []*Topic) error { return enc.Encode(entries) } -// writeSearchPage generates search.html with inline client-side JS search. -// The JS uses escapeHTML() on all data before DOM insertion to prevent XSS. -// Data comes from our own search-index.json, not external user input. -func writeSearchPage(outputDir string) error { - path := filepath.Join(outputDir, "search.html") - f, err := os.Create(path) - if err != nil { - return err - } - defer f.Close() - - // Render via a search template with empty results + inject client-side JS. - data := searchData{Query: "", Results: nil} - if err := renderPage(f, "search.html", data); err != nil { - return err - } - - // Append inline script for client-side search. - _, err = f.WriteString(clientSearchScript) - return err -} - // clientSearchScript is the inline JS for static-site client-side search. // All values are escaped via escapeHTML() before DOM insertion. // The search index is generated from our own catalog data, not user input. diff --git a/pkg/help/generate_test.go b/pkg/help/generate_test.go index 50033bc..4a8b900 100644 --- a/pkg/help/generate_test.go +++ b/pkg/help/generate_test.go @@ -127,7 +127,7 @@ func TestGenerate_Good_404Exists(t *testing.T) { html := string(content) assert.Contains(t, html, "404") - assert.Contains(t, html, "not found") + assert.Contains(t, html, "Not Found") } func TestGenerate_Good_EmptyDir(t *testing.T) {