Preserve README markdown presentation
- SHA
c79744be94feee42a2fe94de3f34f2e9302c7bb3- Parents
-
1263f26 - Tree
9c78bd6
c79744b
c79744be94feee42a2fe94de3f34f2e9302c7bb31263f26
9c78bd6| Status | File | + | - |
|---|---|---|---|
| M |
internal/markdown/markdown_test.go
|
5 | 0 |
| M |
internal/markdown/render.go
|
11 | 0 |
| M |
internal/markdown/sanitize.go
|
2 | 0 |
| M |
internal/web/handlers/repo/code.go
|
2 | 2 |
internal/markdown/markdown_test.gomodified@@ -157,6 +157,11 @@ func TestRender_AllowsSafeHTML(t *testing.T) { | ||
| 157 | 157 | "# Hello world", |
| 158 | 158 | []string{`id="hello-world"`}, |
| 159 | 159 | }, |
| 160 | + { | |
| 161 | + "readme presentation html", | |
| 162 | + `<p align="center"><img src="logo.svg" alt="" width="120"></p><h1 align="center">shithub</h1>`, | |
| 163 | + []string{`<p align="center">`, `<img`, `src="logo.svg"`, `width="120"`, `<h1 align="center">`}, | |
| 164 | + }, | |
| 160 | 165 | { |
| 161 | 166 | "GFM table", |
| 162 | 167 | "| a | b |\n|---|---|\n| 1 | 2 |\n", |
internal/markdown/render.gomodified@@ -129,3 +129,14 @@ func RenderHTML(src []byte) (string, error) { | ||
| 129 | 129 | } |
| 130 | 130 | return string(out), nil |
| 131 | 131 | } |
| 132 | + | |
| 133 | +// RenderDocumentHTML renders README-like markdown where source line | |
| 134 | +// wrapping should remain semantic markdown, not comment-style hard | |
| 135 | +// breaks. | |
| 136 | +func RenderDocumentHTML(src []byte) (string, error) { | |
| 137 | + out, _, _, err := Render(context.Background(), src, Options{SoftBreakAsBR: false}) | |
| 138 | + if err != nil { | |
| 139 | + return "", err | |
| 140 | + } | |
| 141 | + return string(out), nil | |
| 142 | +} | |
internal/markdown/sanitize.gomodified@@ -27,6 +27,7 @@ var sanitizer = func() *bluemonday.Policy { | ||
| 27 | 27 | |
| 28 | 28 | // Headings keep their auto-generated id so anchor links work. |
| 29 | 29 | p.AllowAttrs("id").OnElements("h1", "h2", "h3", "h4", "h5", "h6") |
| 30 | + p.AllowAttrs("align").Matching(reAlign).OnElements("p", "div", "h1", "h2", "h3", "h4", "h5", "h6") | |
| 30 | 31 | |
| 31 | 32 | // Code-block class allowlist for Chroma (`language-foo`). The |
| 32 | 33 | // SpaceSeparatedTokens matcher is bluemonday-built-in; we |
@@ -67,6 +68,7 @@ var sanitizer = func() *bluemonday.Policy { | ||
| 67 | 68 | }() |
| 68 | 69 | |
| 69 | 70 | var reCodeClass = regexp.MustCompile(`^(?:language-[A-Za-z0-9_+\-]+|chroma|chroma-[a-zA-Z]+|nl|ln|line|hl)(?:\s+(?:language-[A-Za-z0-9_+\-]+|chroma|chroma-[a-zA-Z]+|nl|ln|line|hl))*$`) |
| 71 | +var reAlign = regexp.MustCompile(`^(?:left|center|right)$`) | |
| 70 | 72 | |
| 71 | 73 | // sanitizeBytes is the hot-path entry the Render pipeline uses. The |
| 72 | 74 | // bluemonday Policy is built at package init via the var initializer |
internal/web/handlers/repo/code.gomodified@@ -228,7 +228,7 @@ func (h *Handlers) findAndRenderREADME(r *http.Request, cc *codeContext, entries | ||
| 228 | 228 | } |
| 229 | 229 | // Markdown: render via Goldmark + sanitizer. |
| 230 | 230 | if hasExt(lower, []string{".md", ".markdown"}) { |
| 231 | - out, mderr := mdrender.RenderHTML(body) | |
| 231 | + out, mderr := mdrender.RenderDocumentHTML(body) | |
| 232 | 232 | if mderr == nil { |
| 233 | 233 | return out |
| 234 | 234 | } |
@@ -302,7 +302,7 @@ func (h *Handlers) codeBlob(w http.ResponseWriter, r *http.Request) { | ||
| 302 | 302 | // Text path: highlight or markdown-render. |
| 303 | 303 | if hasExt(strings.ToLower(cc.subpath), []string{".md", ".markdown"}) { |
| 304 | 304 | data["IsMarkdown"] = true |
| 305 | - rendered, mderr := mdrender.RenderHTML(body) | |
| 305 | + rendered, mderr := mdrender.RenderDocumentHTML(body) | |
| 306 | 306 | if mderr == nil { |
| 307 | 307 | data["MarkdownHTML"] = template.HTML(rendered) //nolint:gosec // sanitized |
| 308 | 308 | } |