Merge PR #1510
This commit is contained in:
commit
93c12da47a
2 changed files with 107 additions and 16 deletions
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gomarkdown/markdown"
|
"github.com/gomarkdown/markdown"
|
||||||
|
mdast "github.com/gomarkdown/markdown/ast"
|
||||||
mdhtml "github.com/gomarkdown/markdown/html"
|
mdhtml "github.com/gomarkdown/markdown/html"
|
||||||
"github.com/gomarkdown/markdown/parser"
|
"github.com/gomarkdown/markdown/parser"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
|
|
@ -41,6 +42,36 @@ const (
|
||||||
|
|
||||||
var matrixMentionHrefRegexp = regexp.MustCompile(`(?i)<a[^>]+href=["']([^"']+)["']`)
|
var matrixMentionHrefRegexp = regexp.MustCompile(`(?i)<a[^>]+href=["']([^"']+)["']`)
|
||||||
|
|
||||||
|
// matrixHTMLRenderer wraps the standard gomarkdown HTML renderer and suppresses <p>
|
||||||
|
// wrappers throughout the document. Matrix clients apply default browser paragraph
|
||||||
|
// margins to <p> elements, which stack with the margins of surrounding block elements
|
||||||
|
// (lists, code blocks, headings) and produce excessive vertical spacing. Instead we
|
||||||
|
// emit a single <br> between consecutive paragraphs for visual separation; adjacent
|
||||||
|
// block-level elements already carry their own CSS margin.
|
||||||
|
type matrixHTMLRenderer struct {
|
||||||
|
*mdhtml.Renderer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *matrixHTMLRenderer) RenderNode(w io.Writer, node mdast.Node, entering bool) mdast.WalkStatus {
|
||||||
|
if _, ok := node.(*mdast.Paragraph); ok {
|
||||||
|
if !entering {
|
||||||
|
// Between consecutive paragraphs emit a line break for separation.
|
||||||
|
// Before block siblings (lists, headings, code blocks) their own margin suffices.
|
||||||
|
siblings := node.GetParent().GetChildren()
|
||||||
|
for i, child := range siblings {
|
||||||
|
if child == node && i+1 < len(siblings) {
|
||||||
|
if _, ok := siblings[i+1].(*mdast.Paragraph); ok {
|
||||||
|
io.WriteString(w, "<br>") //nolint:errcheck
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mdast.GoToNext
|
||||||
|
}
|
||||||
|
return r.Renderer.RenderNode(w, node, entering)
|
||||||
|
}
|
||||||
|
|
||||||
type roomKindCacheEntry struct {
|
type roomKindCacheEntry struct {
|
||||||
isGroup bool
|
isGroup bool
|
||||||
expiresAt time.Time
|
expiresAt time.Time
|
||||||
|
|
@ -273,8 +304,9 @@ func (c *MatrixChannel) Stop(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func markdownToHTML(md string) string {
|
func markdownToHTML(md string) string {
|
||||||
p := parser.NewWithExtensions(parser.CommonExtensions | parser.AutoHeadingIDs)
|
extensions := (parser.CommonExtensions | parser.AutoHeadingIDs) &^ parser.DefinitionLists
|
||||||
renderer := mdhtml.NewRenderer(mdhtml.RendererOptions{Flags: mdhtml.CommonFlags})
|
p := parser.NewWithExtensions(extensions)
|
||||||
|
renderer := &matrixHTMLRenderer{mdhtml.NewRenderer(mdhtml.RendererOptions{Flags: mdhtml.CommonFlags})}
|
||||||
return strings.TrimSpace(string(markdown.ToHTML([]byte(md), p, renderer)))
|
return strings.TrimSpace(string(markdown.ToHTML([]byte(md), p, renderer)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -340,23 +340,82 @@ func TestMatrixOutboundContent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkdownToHTML(t *testing.T) {
|
func TestMarkdownToHTML(t *testing.T) {
|
||||||
tests := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
md string
|
||||||
contains string
|
rendered string
|
||||||
}{
|
}{
|
||||||
{"bold", "**hello**", "<strong>hello</strong>"},
|
{
|
||||||
{"italic", "_world_", "<em>world</em>"},
|
name: "bold",
|
||||||
{"header", "### Title", "<h3"},
|
md: "**hello**",
|
||||||
{"code block", "```\nfoo()\n```", "<code>"},
|
rendered: `<strong>hello</strong>`,
|
||||||
{"inline code", "`x`", "<code>x</code>"},
|
},
|
||||||
{"plain text", "just text", "just text"},
|
{
|
||||||
|
name: "italic",
|
||||||
|
md: "_world_",
|
||||||
|
rendered: `<em>world</em>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "heading",
|
||||||
|
md: "### Title",
|
||||||
|
rendered: `<h3 id="title">Title</h3>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fenced code block",
|
||||||
|
md: "```\nfoo()\n```",
|
||||||
|
rendered: "<pre><code>foo()\n</code></pre>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inline code",
|
||||||
|
md: "`x`",
|
||||||
|
rendered: `<code>x</code>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "plain text has no block wrapper",
|
||||||
|
md: "just text",
|
||||||
|
rendered: `just text`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "loose list has no <p> wrapper around items",
|
||||||
|
md: "- Item one\n\n- Item two\n",
|
||||||
|
rendered: `<ul>
|
||||||
|
<li>Item one</li>
|
||||||
|
|
||||||
|
<li>Item two</li>
|
||||||
|
</ul>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list item with nested sublist has no <p> wrapper",
|
||||||
|
md: "1. Steps overview:\n\n - Step A\n - Step B\n",
|
||||||
|
rendered: `<ol>
|
||||||
|
<li>Steps overview:
|
||||||
|
<ul>
|
||||||
|
<li>Step A</li>
|
||||||
|
<li>Step B</li>
|
||||||
|
</ul></li>
|
||||||
|
</ol>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tight list has no <p> wrapper",
|
||||||
|
md: "- Alpha\n- Beta\n",
|
||||||
|
rendered: `<ul>
|
||||||
|
<li>Alpha</li>
|
||||||
|
<li>Beta</li>
|
||||||
|
</ul>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "paragraph before list has no <p> wrapper",
|
||||||
|
md: "Introduction text.\n\n- Point one\n",
|
||||||
|
rendered: `Introduction text.<ul>
|
||||||
|
<li>Point one</li>
|
||||||
|
</ul>`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
for _, tc := range cases {
|
||||||
got := markdownToHTML(tt.input)
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if !strings.Contains(got, tt.contains) {
|
if got := markdownToHTML(tc.md); got != tc.rendered {
|
||||||
t.Fatalf("markdownToHTML(%q) = %q, want it to contain %q", tt.input, got, tt.contains)
|
t.Fatalf("markdownToHTML(%q)\n got: %q\nwant: %q", tc.md, got, tc.rendered)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue