tenseleyflow/shithub / c79744b

Browse files

Preserve README markdown presentation

Authored by espadonne
SHA
c79744be94feee42a2fe94de3f34f2e9302c7bb3
Parents
1263f26
Tree
9c78bd6

4 changed files

StatusFile+-
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) {
157157
 			"# Hello world",
158158
 			[]string{`id="hello-world"`},
159159
 		},
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
+		},
160165
 		{
161166
 			"GFM table",
162167
 			"| a | b |\n|---|---|\n| 1 | 2 |\n",
internal/markdown/render.gomodified
@@ -129,3 +129,14 @@ func RenderHTML(src []byte) (string, error) {
129129
 	}
130130
 	return string(out), nil
131131
 }
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 {
2727
 
2828
 	// Headings keep their auto-generated id so anchor links work.
2929
 	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")
3031
 
3132
 	// Code-block class allowlist for Chroma (`language-foo`). The
3233
 	// SpaceSeparatedTokens matcher is bluemonday-built-in; we
@@ -67,6 +68,7 @@ var sanitizer = func() *bluemonday.Policy {
6768
 }()
6869
 
6970
 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)$`)
7072
 
7173
 // sanitizeBytes is the hot-path entry the Render pipeline uses. The
7274
 // 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
228228
 		}
229229
 		// Markdown: render via Goldmark + sanitizer.
230230
 		if hasExt(lower, []string{".md", ".markdown"}) {
231
-			out, mderr := mdrender.RenderHTML(body)
231
+			out, mderr := mdrender.RenderDocumentHTML(body)
232232
 			if mderr == nil {
233233
 				return out
234234
 			}
@@ -302,7 +302,7 @@ func (h *Handlers) codeBlob(w http.ResponseWriter, r *http.Request) {
302302
 	// Text path: highlight or markdown-render.
303303
 	if hasExt(strings.ToLower(cc.subpath), []string{".md", ".markdown"}) {
304304
 		data["IsMarkdown"] = true
305
-		rendered, mderr := mdrender.RenderHTML(body)
305
+		rendered, mderr := mdrender.RenderDocumentHTML(body)
306306
 		if mderr == nil {
307307
 			data["MarkdownHTML"] = template.HTML(rendered) //nolint:gosec // sanitized
308308
 		}