Scheme-aware highlighting everywhere code renders !59
5 files changed, +66 −13
Layout: unified · split
e2e/design_test.go +4 −2
| @@ -28,7 +28,8 @@ func TestReadmeRelativeLinks(t *testing.T) { | |||
| 28 | "# site\n\n[guide](docs/guide.md) and [export](docs/paper.html) and "+ | 28 | "# site\n\n[guide](docs/guide.md) and [export](docs/paper.html) and "+ |
| 29 | "[abs](https://example.org/x) here\n\n\n"+ | 29 | "[abs](https://example.org/x) here\n\n\n"+ |
| 30 | "\n\n"+ | 30 | "\n\n"+ |
| 31 | "| flag | effect |\n|------|--------|\n| `-v` | verbose |\n"), 0o644) | 31 | "| flag | effect |\n|------|--------|\n| `-v` | verbose |\n\n"+ |
| 32 | "```go\nfunc main() {}\n```\n"), 0o644) | ||
| 32 | os.WriteFile(filepath.Join(dir, "docs", "guide.md"), []byte("# guide\n"), 0o644) | 33 | os.WriteFile(filepath.Join(dir, "docs", "guide.md"), []byte("# guide\n"), 0o644) |
| 33 | os.WriteFile(filepath.Join(dir, "docs", "paper.org"), []byte("* paper\n"), 0o644) | 34 | os.WriteFile(filepath.Join(dir, "docs", "paper.org"), []byte("* paper\n"), 0o644) |
| 34 | os.WriteFile(filepath.Join(dir, "img", "logo.png"), []byte{0x89, 0x50}, 0o644) | 35 | os.WriteFile(filepath.Join(dir, "img", "logo.png"), []byte{0x89, 0x50}, 0o644) |
| @@ -48,6 +49,7 @@ func TestReadmeRelativeLinks(t *testing.T) { | |||
| 48 | `src="https://example.org/pic.png"`, // remote image untouched | 49 | `src="https://example.org/pic.png"`, // remote image untouched |
| 49 | `href="https://example.org/x"`, // absolute untouched | 50 | `href="https://example.org/x"`, // absolute untouched |
| 50 | "<table>", "<td>verbose</td>", // GFM table renders | 51 | "<table>", "<td>verbose</td>", // GFM table renders |
| 52 | `<span class="kd">func</span>`, // fenced code highlighted via classes | ||
| 51 | `href="/alice/site/blob/main/README.md">README.md</a>`, // clickable card header | 53 | `href="/alice/site/blob/main/README.md">README.md</a>`, // clickable card header |
| 52 | `<th>name</th>`, // file table column headers | 54 | `<th>name</th>`, // file table column headers |
| 53 | } { | 55 | } { |
| @@ -108,7 +110,7 @@ func TestReadmeRelativeLinks(t *testing.T) { | |||
| 108 | t.Errorf("highlighting not class-based:\n%.2000s", body) | 110 | t.Errorf("highlighting not class-based:\n%.2000s", body) |
| 109 | } | 111 | } |
| 110 | if _, css := inst.get(t, "/static/style.css"); strings.Count(css, "/* Background */") < 2 || | 112 | if _, css := inst.get(t, "/static/style.css"); strings.Count(css, "/* Background */") < 2 || |
| 111 | !strings.Contains(css, ".chroma, .bg { background: var(--code-bg)") { | 113 | !strings.Contains(css, ".chroma, .bg { background: transparent") { |
| 112 | t.Error("stylesheet missing dual syntax palettes") | 114 | t.Error("stylesheet missing dual syntax palettes") |
| 113 | } | 115 | } |
| 114 | // Explore rows carry topics, license, and updated date. | 116 | // Explore rows carry topics, license, and updated date. |
go.mod +2 −1
| @@ -10,6 +10,8 @@ require ( | |||
| 10 | github.com/niklasfasching/go-org v1.9.1 | 10 | github.com/niklasfasching/go-org v1.9.1 |
| 11 | github.com/spf13/cobra v1.10.2 | 11 | github.com/spf13/cobra v1.10.2 |
| 12 | github.com/yuin/goldmark v1.8.5 | 12 | github.com/yuin/goldmark v1.8.5 |
| 13 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc | ||
| 14 | go.yaml.in/yaml/v3 v3.0.4 | ||
| 13 | golang.org/x/crypto v0.55.0 | 15 | golang.org/x/crypto v0.55.0 |
| 14 | golang.org/x/net v0.57.0 | 16 | golang.org/x/net v0.57.0 |
| 15 | golang.org/x/term v0.45.0 | 17 | golang.org/x/term v0.45.0 |
| @@ -30,7 +32,6 @@ require ( | |||
| 30 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | 32 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect |
| 31 | github.com/russross/blackfriday/v2 v2.1.0 // indirect | 33 | github.com/russross/blackfriday/v2 v2.1.0 // indirect |
| 32 | github.com/spf13/pflag v1.0.9 // indirect | 34 | github.com/spf13/pflag v1.0.9 // indirect |
| 33 | go.yaml.in/yaml/v3 v3.0.4 // indirect | ||
| 34 | golang.org/x/sys v0.47.0 // indirect | 35 | golang.org/x/sys v0.47.0 // indirect |
| 35 | golang.org/x/text v0.41.0 // indirect | 36 | golang.org/x/text v0.41.0 // indirect |
| 36 | modernc.org/libc v1.74.4 // indirect | 37 | modernc.org/libc v1.74.4 // indirect |
go.sum +12
| @@ -4,8 +4,10 @@ github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67 | |||
| 4 | github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= | 4 | github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= |
| 5 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= | 5 | github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= |
| 6 | github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= | 6 | github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= |
| 7 | github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= | ||
| 7 | github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs= | 8 | github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs= |
| 8 | github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8= | 9 | github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8= |
| 10 | github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= | ||
| 9 | github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= | 11 | github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= |
| 10 | github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= | 12 | github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= |
| 11 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= | 13 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= |
| @@ -14,6 +16,10 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg | |||
| 14 | github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= | 16 | github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= |
| 15 | github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= | 17 | github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= |
| 16 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= | 18 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= |
| 19 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| 20 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| 21 | github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= | ||
| 22 | github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= | ||
| 17 | github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0= | 23 | github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0= |
| 18 | github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= | 24 | github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= |
| 19 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= | 25 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= |
| @@ -48,8 +54,13 @@ github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= | |||
| 48 | github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= | 54 | github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= |
| 49 | github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= | 55 | github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= |
| 50 | github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | 56 | github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= |
| 57 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
| 58 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
| 59 | github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||
| 51 | github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= | 60 | github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= |
| 52 | github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= | 61 | github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= |
| 62 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= | ||
| 63 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= | ||
| 53 | go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= | 64 | go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= |
| 54 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= | 65 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= |
| 55 | golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= | 66 | golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= |
| @@ -70,6 +81,7 @@ golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= | |||
| 70 | golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= | 81 | golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= |
| 71 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | 82 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= |
| 72 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | 83 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
| 84 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
| 73 | modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI= | 85 | modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI= |
| 74 | modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= | 86 | modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= |
| 75 | modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= | 87 | modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= |
internal/httpd/web.go +45 −7
| @@ -23,6 +23,7 @@ import ( | |||
| 23 | "github.com/microcosm-cc/bluemonday" | 23 | "github.com/microcosm-cc/bluemonday" |
| 24 | "github.com/niklasfasching/go-org/org" | 24 | "github.com/niklasfasching/go-org/org" |
| 25 | "github.com/yuin/goldmark" | 25 | "github.com/yuin/goldmark" |
| 26 | highlighting "github.com/yuin/goldmark-highlighting/v2" | ||
| 26 | "github.com/yuin/goldmark/extension" | 27 | "github.com/yuin/goldmark/extension" |
| 27 | 28 | ||
| 28 | "gitbay.org/gitbay/internal/autolink" | 29 | "gitbay.org/gitbay/internal/autolink" |
| @@ -765,7 +766,7 @@ var chromaCSS = func() []byte { | |||
| 765 | chromaFormatter.WriteCSS(&buf, styles.Get("friendly")) | 766 | chromaFormatter.WriteCSS(&buf, styles.Get("friendly")) |
| 766 | buf.WriteString("\n@media (prefers-color-scheme: dark) {\n") | 767 | buf.WriteString("\n@media (prefers-color-scheme: dark) {\n") |
| 767 | chromaFormatter.WriteCSS(&buf, styles.Get("github-dark")) | 768 | chromaFormatter.WriteCSS(&buf, styles.Get("github-dark")) |
| 768 | buf.WriteString("}\n.chroma, .bg { background: var(--code-bg) !important; }\n") | 769 | buf.WriteString("}\n.chroma, .bg { background: transparent !important; }\n") |
| 769 | return buf.Bytes() | 770 | return buf.Bytes() |
| 770 | }() | 771 | }() |
| 771 | 772 | ||
| @@ -828,8 +829,30 @@ func pickReadme(entries []gitutil.TreeEntry) string { | |||
| 828 | } | 829 | } |
| 829 | 830 | ||
| 830 | // markdown is the shared renderer: GFM (tables, strikethrough, autolinks, | 831 | // markdown is the shared renderer: GFM (tables, strikethrough, autolinks, |
| 831 | // task lists) on top of CommonMark. Raw HTML is still dropped. | 832 | // task lists) on top of CommonMark, with class-based fence highlighting |
| 832 | var markdown = goldmark.New(goldmark.WithExtensions(extension.GFM)) | 833 | // (the palette lives in the stylesheet, per scheme). Raw HTML is still |
| 834 | // dropped. | ||
| 835 | var markdown = goldmark.New(goldmark.WithExtensions(extension.GFM, | ||
| 836 | highlighting.NewHighlighting(highlighting.WithFormatOptions(html.WithClasses(true))))) | ||
| 837 | |||
| 838 | // fenceHighlight renders one code block with chroma classes, for org and | ||
| 839 | // anything else outside goldmark. Unknown languages fall back to plain. | ||
| 840 | func fenceHighlight(source, lang string) string { | ||
| 841 | lexer := lexers.Get(lang) | ||
| 842 | if lexer == nil { | ||
| 843 | lexer = lexers.Fallback | ||
| 844 | } | ||
| 845 | iterator, err := lexer.Tokenise(nil, source) | ||
| 846 | if err != nil { | ||
| 847 | return "<pre>" + template.HTMLEscapeString(source) + "</pre>" | ||
| 848 | } | ||
| 849 | var buf bytes.Buffer | ||
| 850 | f := html.New(html.WithClasses(true)) | ||
| 851 | if err := f.Format(&buf, styles.Get("friendly"), iterator); err != nil { | ||
| 852 | return "<pre>" + template.HTMLEscapeString(source) + "</pre>" | ||
| 853 | } | ||
| 854 | return buf.String() | ||
| 855 | } | ||
| 833 | 856 | ||
| 834 | // mdHTML renders user-authored markdown (issue and MR bodies, comments). | 857 | // mdHTML renders user-authored markdown (issue and MR bodies, comments). |
| 835 | // goldmark's default renderer drops raw HTML, so this is safe as-is. | 858 | // goldmark's default renderer drops raw HTML, so this is safe as-is. |
| @@ -921,8 +944,16 @@ func renderComments(cs []store.IssueComment, md func(string) template.HTML) []re | |||
| 921 | 944 | ||
| 922 | // ugcPolicy sanitizes rendered repo content before it enters the forge's | 945 | // ugcPolicy sanitizes rendered repo content before it enters the forge's |
| 923 | // origin: markdown is already safe (goldmark drops raw HTML), but org-mode | 946 | // origin: markdown is already safe (goldmark drops raw HTML), but org-mode |
| 924 | // output and repo-authored HTML are not. | 947 | // output and repo-authored HTML are not. Chroma's highlighting classes |
| 925 | var ugcPolicy = bluemonday.UGCPolicy() | 948 | // must survive; the pattern admits only short token codes, not the site's |
| 949 | // own class names. | ||
| 950 | var ugcPolicy = func() *bluemonday.Policy { | ||
| 951 | p := bluemonday.UGCPolicy() | ||
| 952 | p.AllowAttrs("class"). | ||
| 953 | Matching(regexp.MustCompile(`^(chroma|[a-z0-9]{1,3})( (chroma|[a-z0-9]{1,3}))*$`)). | ||
| 954 | OnElements("span", "pre", "code", "div") | ||
| 955 | return p | ||
| 956 | }() | ||
| 926 | 957 | ||
| 927 | // renderReadme renders a README by extension: markdown, org-mode, and | 958 | // renderReadme renders a README by extension: markdown, org-mode, and |
| 928 | // (sanitized) HTML richly; everything else as escaped plaintext. | 959 | // (sanitized) HTML richly; everything else as escaped plaintext. |
| @@ -942,11 +973,18 @@ func renderReadme(name string, raw []byte) template.HTML { | |||
| 942 | return template.HTML(buf.String()) | 973 | return template.HTML(buf.String()) |
| 943 | case ".org": | 974 | case ".org": |
| 944 | doc := org.New().Parse(bytes.NewReader(raw), name) | 975 | doc := org.New().Parse(bytes.NewReader(raw), name) |
| 945 | html, err := doc.Write(org.NewHTMLWriter()) | 976 | writer := org.NewHTMLWriter() |
| 977 | writer.HighlightCodeBlock = func(source, lang string, inline bool, params map[string]string) string { | ||
| 978 | if inline { | ||
| 979 | return "<code>" + template.HTMLEscapeString(source) + "</code>" | ||
| 980 | } | ||
| 981 | return fenceHighlight(source, lang) | ||
| 982 | } | ||
| 983 | out, err := doc.Write(writer) | ||
| 946 | if err != nil { | 984 | if err != nil { |
| 947 | return plain() | 985 | return plain() |
| 948 | } | 986 | } |
| 949 | return template.HTML(ugcPolicy.Sanitize(html)) | 987 | return template.HTML(ugcPolicy.Sanitize(out)) |
| 950 | case ".html", ".htm": | 988 | case ".html", ".htm": |
| 951 | return template.HTML(ugcPolicy.Sanitize(string(raw))) | 989 | return template.HTML(ugcPolicy.Sanitize(string(raw))) |
| 952 | default: | 990 | default: |
internal/web/static/style.css +3 −3
| @@ -457,9 +457,9 @@ button.linklike:hover { text-decoration: underline; filter: none; } | |||
| 457 | margin-top: var(--sp-4); | 457 | margin-top: var(--sp-4); |
| 458 | overflow-x: auto; | 458 | overflow-x: auto; |
| 459 | } | 459 | } |
| 460 | /* chroma emits inline styles for a light background; pin code blocks to | 460 | /* Highlighting is class-based with a palette per scheme, so code blocks |
| 461 | light colors in both schemes so unstyled tokens stay legible. */ | 461 | follow the site's own colors. */ |
| 462 | .code { background: #f8f8f8; color: #1a1a1a; padding: var(--sp-3) var(--sp-4); color-scheme: light; } | 462 | .code { background: var(--code-bg); color: var(--fg); padding: var(--sp-3) var(--sp-4); } |
| 463 | .code pre { margin: 0; background: transparent !important; } | 463 | .code pre { margin: 0; background: transparent !important; } |
| 464 | @media (prefers-color-scheme: dark) { | 464 | @media (prefers-color-scheme: dark) { |
| 465 | .code { border-color: var(--line); } | 465 | .code { border-color: var(--line); } |