diff --git a/go.mod b/go.mod index ba3724b78..5d3fd9e73 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( github.com/tdewolff/minify/v2 v2.20.37 github.com/tdewolff/parse/v2 v2.7.15 github.com/tetratelabs/wazero v1.8.1 - github.com/yuin/goldmark v1.7.4 + github.com/yuin/goldmark v1.7.8 github.com/yuin/goldmark-emoji v1.0.4 go.uber.org/automaxprocs v1.5.3 gocloud.dev v0.39.0 diff --git a/go.sum b/go.sum index 814fa1221..84c6452cd 100644 --- a/go.sum +++ b/go.sum @@ -472,6 +472,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= +github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-emoji v1.0.4 h1:vCwMkPZSNefSUnOW2ZKRUjBSD5Ok3W78IXhGxxAEF90= github.com/yuin/goldmark-emoji v1.0.4/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go index ea3bbc4ae..823a43c9d 100644 --- a/markup/goldmark/convert.go +++ b/markup/goldmark/convert.go @@ -43,6 +43,7 @@ import ( ) const ( + // Don't change this. This pattern is lso used in the image render hooks. internalAttrPrefix = "_h__" ) diff --git a/markup/goldmark/internal/render/context.go b/markup/goldmark/internal/render/context.go index b8cf9ba54..cd64fc944 100644 --- a/markup/goldmark/internal/render/context.go +++ b/markup/goldmark/internal/render/context.go @@ -16,9 +16,13 @@ package render import ( "bytes" "math/bits" + "strings" "sync" + bp "github.com/gohugoio/hugo/bufferpool" + htext "github.com/gohugoio/hugo/common/text" + "github.com/gohugoio/hugo/tpl" "github.com/gohugoio/hugo/markup/converter" "github.com/gohugoio/hugo/markup/converter/hooks" @@ -258,3 +262,30 @@ func (c *hookBase) Position() htext.Position { func (c *hookBase) PositionerSourceTarget() []byte { return c.getSourceSample() } + +// TextPlain returns a plain text representation of the given node. +// Goldmark's Node.Text was deprecated in 1.7.8. +func TextPlain(n ast.Node, source []byte) string { + buf := bp.GetBuffer() + defer bp.PutBuffer(buf) + + for c := n.FirstChild(); c != nil; c = c.NextSibling() { + textPlainTo(c, source, buf) + } + return buf.String() +} + +func textPlainTo(c ast.Node, source []byte, buf *bytes.Buffer) { + if c == nil { + return + } + switch c := c.(type) { + case *ast.RawHTML: + s := strings.TrimSpace(tpl.StripHTML(string(c.Segments.Value(source)))) + buf.WriteString(s) + case *ast.Text: + buf.Write(c.Segment.Value(source)) + default: + textPlainTo(c.FirstChild(), source, buf) + } +} diff --git a/markup/goldmark/render_hooks.go b/markup/goldmark/render_hooks.go index bacb41a37..12cf00455 100644 --- a/markup/goldmark/render_hooks.go +++ b/markup/goldmark/render_hooks.go @@ -200,7 +200,7 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N destination: string(n.Destination), title: string(n.Title), text: hstring.HTML(text), - plainText: string(n.Text(source)), + plainText: render.TextPlain(n, source), AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerGeneral), }, ordinal: ordinal, @@ -223,7 +223,7 @@ func (r *hookedRenderer) filterInternalAttributes(attrs []ast.Attribute) []ast.A } // Fall back to the default Goldmark render funcs. Method below borrowed from: -// https://github.com/yuin/goldmark/blob/b611cd333a492416b56aa8d94b04a67bf0096ab2/renderer/html/html.go#L404 +// https://github.com/yuin/goldmark func (r *hookedRenderer) renderImageDefault(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil @@ -234,7 +234,7 @@ func (r *hookedRenderer) renderImageDefault(w util.BufWriter, source []byte, nod _, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true))) } _, _ = w.WriteString(`" alt="`) - _, _ = w.Write(nodeToHTMLText(n, source)) + r.renderTexts(w, source, n) _ = w.WriteByte('"') if n.Title != nil { _, _ = w.WriteString(` title="`) @@ -242,8 +242,7 @@ func (r *hookedRenderer) renderImageDefault(w util.BufWriter, source []byte, nod _ = w.WriteByte('"') } if n.Attributes() != nil { - attrs := r.filterInternalAttributes(n.Attributes()) - attributes.RenderASTAttributes(w, attrs...) + html.RenderAttributes(w, n, html.ImageAttributeFilter) } if r.XHTML { _, _ = w.WriteString(" />") @@ -289,7 +288,7 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No destination: string(n.Destination), title: string(n.Title), text: hstring.HTML(text), - plainText: string(n.Text(source)), + plainText: render.TextPlain(n, source), AttributesHolder: attributes.Empty, }, ) @@ -297,6 +296,79 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No return ast.WalkContinue, err } +// Borrowed from Goldmark's HTML renderer. +func (r *hookedRenderer) renderTexts(w util.BufWriter, source []byte, n ast.Node) { + for c := n.FirstChild(); c != nil; c = c.NextSibling() { + if s, ok := c.(*ast.String); ok { + _, _ = r.renderString(w, source, s, true) + } else if t, ok := c.(*ast.Text); ok { + _, _ = r.renderText(w, source, t, true) + } else { + r.renderTexts(w, source, c) + } + } +} + +// Borrowed from Goldmark's HTML renderer. +func (r *hookedRenderer) renderString(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + n := node.(*ast.String) + if n.IsCode() { + _, _ = w.Write(n.Value) + } else { + if n.IsRaw() { + r.Writer.RawWrite(w, n.Value) + } else { + r.Writer.Write(w, n.Value) + } + } + return ast.WalkContinue, nil +} + +// Borrowed from Goldmark's HTML renderer. +func (r *hookedRenderer) renderText(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + n := node.(*ast.Text) + segment := n.Segment + if n.IsRaw() { + r.Writer.RawWrite(w, segment.Value(source)) + } else { + value := segment.Value(source) + r.Writer.Write(w, value) + if n.HardLineBreak() || (n.SoftLineBreak() && r.HardWraps) { + if r.XHTML { + _, _ = w.WriteString("
\n") + } else { + _, _ = w.WriteString("
\n") + } + } else if n.SoftLineBreak() { + // TODO(bep) we use these methods a fallback to default rendering when no image/link hooks are defined. + // I don't think the below is relevant in these situations, but if so, we need to create a PR + // upstream to export softLineBreak. + /*if r.EastAsianLineBreaks != html.EastAsianLineBreaksNone && len(value) != 0 { + sibling := node.NextSibling() + if sibling != nil && sibling.Kind() == ast.KindText { + if siblingText := sibling.(*ast.Text).Value(source); len(siblingText) != 0 { + thisLastRune := util.ToRune(value, len(value)-1) + siblingFirstRune, _ := utf8.DecodeRune(siblingText) + if r.EastAsianLineBreaks.softLineBreak(thisLastRune, siblingFirstRune) { + _ = w.WriteByte('\n') + } + } + } + } else { + _ = w.WriteByte('\n') + }*/ + _ = w.WriteByte('\n') + } + } + return ast.WalkContinue, nil +} + // Fall back to the default Goldmark render funcs. Method below borrowed from: // https://github.com/yuin/goldmark/blob/b611cd333a492416b56aa8d94b04a67bf0096ab2/renderer/html/html.go#L404 func (r *hookedRenderer) renderLinkDefault(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { @@ -443,7 +515,7 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast level: n.Level, anchor: string(anchor), text: hstring.HTML(text), - plainText: string(n.Text(source)), + plainText: render.TextPlain(n, source), AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral), }, ) @@ -478,21 +550,3 @@ func (e *links) Extend(m goldmark.Markdown) { util.Prioritized(newLinkRenderer(e.cfg), 100), )) } - -// Borrowed from Goldmark. -func nodeToHTMLText(n ast.Node, source []byte) []byte { - var buf bytes.Buffer - for c := n.FirstChild(); c != nil; c = c.NextSibling() { - if s, ok := c.(*ast.String); ok && s.IsCode() { - buf.Write(s.Text(source)) - } else if !c.HasChildren() { - buf.Write(util.EscapeHTML(c.Text(source))) - if t, ok := c.(*ast.Text); ok && t.SoftLineBreak() { - buf.WriteByte('\n') - } - } else { - buf.Write(nodeToHTMLText(c, source)) - } - } - return buf.Bytes() -}