aboutsummaryrefslogtreecommitdiffstats
path: root/modules/markup/markdown
diff options
context:
space:
mode:
Diffstat (limited to 'modules/markup/markdown')
-rw-r--r--modules/markup/markdown/ast.go53
-rw-r--r--modules/markup/markdown/convertyaml.go43
-rw-r--r--modules/markup/markdown/goldmark.go52
-rw-r--r--modules/markup/markdown/markdown.go56
-rw-r--r--modules/markup/markdown/markdown_attention_test.go14
-rw-r--r--modules/markup/markdown/markdown_benchmark_test.go4
-rw-r--r--modules/markup/markdown/markdown_math_test.go67
-rw-r--r--modules/markup/markdown/markdown_test.go112
-rw-r--r--modules/markup/markdown/math/block_renderer.go6
-rw-r--r--modules/markup/markdown/math/inline_parser.go48
-rw-r--r--modules/markup/markdown/math/inline_renderer.go2
-rw-r--r--modules/markup/markdown/math/math.go21
-rw-r--r--modules/markup/markdown/meta_test.go12
-rw-r--r--modules/markup/markdown/renderconfig.go11
-rw-r--r--modules/markup/markdown/renderconfig_test.go15
-rw-r--r--modules/markup/markdown/toc.go3
-rw-r--r--modules/markup/markdown/transform_blockquote.go5
-rw-r--r--modules/markup/markdown/transform_codespan.go2
-rw-r--r--modules/markup/markdown/transform_heading.go4
-rw-r--r--modules/markup/markdown/transform_image.go59
-rw-r--r--modules/markup/markdown/transform_link.go27
21 files changed, 311 insertions, 305 deletions
diff --git a/modules/markup/markdown/ast.go b/modules/markup/markdown/ast.go
index ca165b1ba0..f29f883734 100644
--- a/modules/markup/markdown/ast.go
+++ b/modules/markup/markdown/ast.go
@@ -4,6 +4,7 @@
package markdown
import (
+ "html/template"
"strconv"
"github.com/yuin/goldmark/ast"
@@ -29,9 +30,7 @@ func (n *Details) Kind() ast.NodeKind {
// NewDetails returns a new Paragraph node.
func NewDetails() *Details {
- return &Details{
- BaseBlock: ast.BaseBlock{},
- }
+ return &Details{}
}
// Summary is a block that contains the summary of details block
@@ -54,9 +53,7 @@ func (n *Summary) Kind() ast.NodeKind {
// NewSummary returns a new Summary node.
func NewSummary() *Summary {
- return &Summary{
- BaseBlock: ast.BaseBlock{},
- }
+ return &Summary{}
}
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
@@ -95,29 +92,6 @@ type Icon struct {
Name []byte
}
-// Dump implements Node.Dump .
-func (n *Icon) Dump(source []byte, level int) {
- m := map[string]string{}
- m["Name"] = string(n.Name)
- ast.DumpHelper(n, source, level, m, nil)
-}
-
-// KindIcon is the NodeKind for Icon
-var KindIcon = ast.NewNodeKind("Icon")
-
-// Kind implements Node.Kind.
-func (n *Icon) Kind() ast.NodeKind {
- return KindIcon
-}
-
-// NewIcon returns a new Paragraph node.
-func NewIcon(name string) *Icon {
- return &Icon{
- BaseInline: ast.BaseInline{},
- Name: []byte(name),
- }
-}
-
// ColorPreview is an inline for a color preview
type ColorPreview struct {
ast.BaseInline
@@ -175,3 +149,24 @@ func NewAttention(attentionType string) *Attention {
AttentionType: attentionType,
}
}
+
+var KindRawHTML = ast.NewNodeKind("RawHTML")
+
+type RawHTML struct {
+ ast.BaseBlock
+ rawHTML template.HTML
+}
+
+func (n *RawHTML) Dump(source []byte, level int) {
+ m := map[string]string{}
+ m["RawHTML"] = string(n.rawHTML)
+ ast.DumpHelper(n, source, level, m, nil)
+}
+
+func (n *RawHTML) Kind() ast.NodeKind {
+ return KindRawHTML
+}
+
+func NewRawHTML(rawHTML template.HTML) *RawHTML {
+ return &RawHTML{rawHTML: rawHTML}
+}
diff --git a/modules/markup/markdown/convertyaml.go b/modules/markup/markdown/convertyaml.go
index 1675b68be2..04664a9c1d 100644
--- a/modules/markup/markdown/convertyaml.go
+++ b/modules/markup/markdown/convertyaml.go
@@ -4,23 +4,22 @@
package markdown
import (
+ "strings"
+
+ "code.gitea.io/gitea/modules/htmlutil"
+ "code.gitea.io/gitea/modules/svg"
+
"github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/ast"
"gopkg.in/yaml.v3"
)
func nodeToTable(meta *yaml.Node) ast.Node {
- for {
- if meta == nil {
- return nil
- }
- switch meta.Kind {
- case yaml.DocumentNode:
- meta = meta.Content[0]
- continue
- default:
- }
- break
+ for meta != nil && meta.Kind == yaml.DocumentNode {
+ meta = meta.Content[0]
+ }
+ if meta == nil {
+ return nil
}
switch meta.Kind {
case yaml.MappingNode:
@@ -72,12 +71,28 @@ func sequenceNodeToTable(meta *yaml.Node) ast.Node {
return table
}
-func nodeToDetails(meta *yaml.Node, icon string) ast.Node {
+func nodeToDetails(g *ASTTransformer, meta *yaml.Node) ast.Node {
+ for meta != nil && meta.Kind == yaml.DocumentNode {
+ meta = meta.Content[0]
+ }
+ if meta == nil {
+ return nil
+ }
+ if meta.Kind != yaml.MappingNode {
+ return nil
+ }
+ var keys []string
+ for i := 0; i < len(meta.Content); i += 2 {
+ if meta.Content[i].Kind == yaml.ScalarNode {
+ keys = append(keys, meta.Content[i].Value)
+ }
+ }
details := NewDetails()
+ details.SetAttributeString(g.renderInternal.SafeAttr("class"), g.renderInternal.SafeValue("frontmatter-content"))
summary := NewSummary()
- summary.AppendChild(summary, NewIcon(icon))
+ summaryInnerHTML := htmlutil.HTMLFormat("%s %s", svg.RenderHTML("octicon-table", 12), strings.Join(keys, ", "))
+ summary.AppendChild(summary, NewRawHTML(summaryInnerHTML))
details.AppendChild(details, summary)
details.AppendChild(details, nodeToTable(meta))
-
return details
}
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index 69c2a96ff1..b28fa9824e 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -5,14 +5,10 @@ package markdown
import (
"fmt"
- "regexp"
- "strings"
- "sync"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/internal"
- "code.gitea.io/gitea/modules/setting"
"github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/ast"
@@ -51,7 +47,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
tocList := make([]Header, 0, 20)
if rc.yamlNode != nil {
- metaNode := rc.toMetaNode()
+ metaNode := rc.toMetaNode(g)
if metaNode != nil {
node.InsertBefore(node, firstChild, metaNode)
}
@@ -68,23 +64,12 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
g.transformHeading(ctx, v, reader, &tocList)
case *ast.Paragraph:
g.applyElementDir(v)
- case *ast.Image:
- g.transformImage(ctx, v)
- case *ast.Link:
- g.transformLink(ctx, v)
case *ast.List:
g.transformList(ctx, v, rc)
case *ast.Text:
if v.SoftLineBreak() && !v.HardLineBreak() {
- // TODO: this was a quite unclear part, old code: `if metas["mode"] != "document" { use comment link break setting }`
- // many places render non-comment contents with no mode=document, then these contents also use comment's hard line break setting
- // especially in many tests.
- markdownLineBreakStyle := ctx.RenderOptions.Metas["markdownLineBreakStyle"]
- if markdownLineBreakStyle == "comment" {
- v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments)
- } else if markdownLineBreakStyle == "document" {
- v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments)
- }
+ newLineHardBreak := ctx.RenderOptions.Metas["markdownNewLineHardBreak"] == "true"
+ v.SetHardLineBreak(newLineHardBreak)
}
case *ast.CodeSpan:
g.transformCodeSpan(ctx, v, reader)
@@ -111,11 +96,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
}
}
-// it is copied from old code, which is quite doubtful whether it is correct
-var reValidIconName = sync.OnceValue(func() *regexp.Regexp {
- return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$")
-})
-
// NewHTMLRenderer creates a HTMLRenderer to render in the gitea form.
func NewHTMLRenderer(renderInternal *internal.RenderInternal, opts ...html.Option) renderer.NodeRenderer {
r := &HTMLRenderer{
@@ -140,11 +120,11 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(ast.KindDocument, r.renderDocument)
reg.Register(KindDetails, r.renderDetails)
reg.Register(KindSummary, r.renderSummary)
- reg.Register(KindIcon, r.renderIcon)
reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
reg.Register(KindAttention, r.renderAttention)
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
+ reg.Register(KindRawHTML, r.renderRawHTML)
}
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
@@ -155,7 +135,7 @@ func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.
if entering {
_, err = w.WriteString("<div")
if err == nil {
- _, err = w.WriteString(fmt.Sprintf(` lang=%q`, val))
+ _, err = fmt.Fprintf(w, ` lang=%q`, val)
}
if err == nil {
_, err = w.WriteRune('>')
@@ -206,30 +186,14 @@ func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.N
return ast.WalkContinue, nil
}
-func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+func (r *HTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
-
- n := node.(*Icon)
-
- name := strings.TrimSpace(strings.ToLower(string(n.Name)))
-
- if len(name) == 0 {
- // skip this
- return ast.WalkContinue, nil
- }
-
- if !reValidIconName().MatchString(name) {
- // skip this
- return ast.WalkContinue, nil
- }
-
- // FIXME: the "icon xxx" is from Fomantic UI, it's really questionable whether it still works correctly
- err := r.renderInternal.FormatWithSafeAttrs(w, `<i class="icon %s"></i>`, name)
+ n := node.(*RawHTML)
+ _, err := w.WriteString(string(r.renderInternal.ProtectSafeAttrs(n.rawHTML)))
if err != nil {
return ast.WalkStop, err
}
-
return ast.WalkContinue, nil
}
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index b5fffccdb9..3b788432ba 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -5,7 +5,7 @@
package markdown
import (
- "fmt"
+ "errors"
"html/template"
"io"
"strings"
@@ -48,7 +48,7 @@ func (l *limitWriter) Write(data []byte) (int, error) {
if err != nil {
return n, err
}
- return n, fmt.Errorf("rendered content too large - truncating render")
+ return n, errors.New("rendered content too large - truncating render")
}
n, err := l.w.Write(data)
l.sum += int64(n)
@@ -86,20 +86,15 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C
preClasses += " is-loading"
}
- err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<pre class="%s">`, preClasses)
- if err != nil {
- return
- }
-
// include language-x class as part of commonmark spec, "chroma" class is used to highlight the code
// the "display" class is used by "js/markup/math.ts" to render the code element as a block
// the "math.ts" strictly depends on the structure: <pre class="code-block is-loading"><code class="language-math display">...</code></pre>
- err = r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<code class="chroma language-%s display">`, languageStr)
+ err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<div class="code-block-container code-overflow-scroll"><pre class="%s"><code class="chroma language-%s display">`, preClasses, languageStr)
if err != nil {
return
}
} else {
- _, err := w.WriteString("</code></pre>")
+ _, err := w.WriteString("</code></pre></div>")
if err != nil {
return
}
@@ -126,11 +121,11 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender {
highlighting.WithWrapperRenderer(r.highlightingRenderer),
),
math.NewExtension(&ctx.RenderInternal, math.Options{
- Enabled: setting.Markdown.EnableMath,
- ParseDollarInline: true,
- ParseDollarBlock: true,
- ParseSquareBlock: true, // TODO: this is a bad syntax "\[ ... \]", it conflicts with normal markdown escaping, it should be deprecated in the future (by some config options)
- // ParseBracketInline: true, // TODO: this is also a bad syntax "\( ... \)", it also conflicts, it should be deprecated in the future
+ Enabled: setting.Markdown.EnableMath,
+ ParseInlineDollar: setting.Markdown.MathCodeBlockOptions.ParseInlineDollar,
+ ParseInlineParentheses: setting.Markdown.MathCodeBlockOptions.ParseInlineParentheses, // this is a bad syntax "\( ... \)", it conflicts with normal markdown escaping
+ ParseBlockDollar: setting.Markdown.MathCodeBlockOptions.ParseBlockDollar,
+ ParseBlockSquareBrackets: setting.Markdown.MathCodeBlockOptions.ParseBlockSquareBrackets, // this is a bad syntax "\[ ... \]", it conflicts with normal markdown escaping
}),
meta.Meta,
),
@@ -159,6 +154,14 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
limit: setting.UI.MaxDisplayFileSize * 3,
}
+ // FIXME: Don't read all to memory, but goldmark doesn't support
+ buf, err := io.ReadAll(input)
+ if err != nil {
+ log.Error("Unable to ReadAll: %v", err)
+ return err
+ }
+ buf = giteautil.NormalizeEOL(buf)
+
// FIXME: should we include a timeout to abort the renderer if it takes too long?
defer func() {
err := recover()
@@ -166,35 +169,20 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
return
}
- log.Warn("Unable to render markdown due to panic in goldmark: %v", err)
- if (!setting.IsProd && !setting.IsInTesting) || log.IsDebug() {
- log.Error("Panic in markdown: %v\n%s", err, log.Stack(2))
- }
+ log.Error("Panic in markdown: %v\n%s", err, log.Stack(2))
+ escapedHTML := template.HTMLEscapeString(giteautil.UnsafeBytesToString(buf))
+ _, _ = output.Write(giteautil.UnsafeStringToBytes(escapedHTML))
}()
- // FIXME: Don't read all to memory, but goldmark doesn't support
pc := newParserContext(ctx)
- buf, err := io.ReadAll(input)
- if err != nil {
- log.Error("Unable to ReadAll: %v", err)
- return err
- }
- buf = giteautil.NormalizeEOL(buf)
// Preserve original length.
bufWithMetadataLength := len(buf)
- rc := &RenderConfig{
- Meta: markup.RenderMetaAsDetails,
- Icon: "table",
- Lang: "",
- }
+ rc := &RenderConfig{Meta: markup.RenderMetaAsDetails}
buf, _ = ExtractMetadataBytes(buf, rc)
- metaLength := bufWithMetadataLength - len(buf)
- if metaLength < 0 {
- metaLength = 0
- }
+ metaLength := max(bufWithMetadataLength-len(buf), 0)
rc.metaLength = metaLength
pc.Set(renderConfigKey, rc)
diff --git a/modules/markup/markdown/markdown_attention_test.go b/modules/markup/markdown/markdown_attention_test.go
index f6ec775b2c..7b54653ec0 100644
--- a/modules/markup/markdown/markdown_attention_test.go
+++ b/modules/markup/markdown/markdown_attention_test.go
@@ -23,6 +23,11 @@ func TestAttention(t *testing.T) {
defer svg.MockIcon("octicon-alert")()
defer svg.MockIcon("octicon-stop")()
+ test := func(input, expected string) {
+ result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
+ assert.NoError(t, err)
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
+ }
renderAttention := func(attention, icon string) string {
tmpl := `<blockquote class="attention-header attention-{attention}"><p><svg class="attention-icon attention-{attention} svg {icon}" width="16" height="16"></svg><strong class="attention-{attention}">{Attention}</strong></p>`
tmpl = strings.ReplaceAll(tmpl, "{attention}", attention)
@@ -31,12 +36,6 @@ func TestAttention(t *testing.T) {
return tmpl
}
- test := func(input, expected string) {
- result, err := markdown.RenderString(markup.NewTestRenderContext(), input)
- assert.NoError(t, err)
- assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
- }
-
test(`
> [!NOTE]
> text
@@ -53,4 +52,7 @@ func TestAttention(t *testing.T) {
// legacy GitHub style
test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
+
+ // edge case (it used to cause panic)
+ test(">\ntext", "<blockquote>\n</blockquote>\n<p>text</p>")
}
diff --git a/modules/markup/markdown/markdown_benchmark_test.go b/modules/markup/markdown/markdown_benchmark_test.go
index 0f7e3eea6f..e08612f064 100644
--- a/modules/markup/markdown/markdown_benchmark_test.go
+++ b/modules/markup/markdown/markdown_benchmark_test.go
@@ -12,14 +12,14 @@ import (
func BenchmarkSpecializedMarkdown(b *testing.B) {
// 240856 4719 ns/op
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
markdown.SpecializedMarkdown(&markup.RenderContext{})
}
}
func BenchmarkMarkdownRender(b *testing.B) {
// 23202 50840 ns/op
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = markdown.RenderString(markup.NewTestRenderContext(), "https://example.com\n- a\n- b\n")
}
}
diff --git a/modules/markup/markdown/markdown_math_test.go b/modules/markup/markdown/markdown_math_test.go
index 813f050965..a75f18d36a 100644
--- a/modules/markup/markdown/markdown_math_test.go
+++ b/modules/markup/markdown/markdown_math_test.go
@@ -8,6 +8,8 @@ import (
"testing"
"code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert"
)
@@ -15,6 +17,7 @@ import (
const nl = "\n"
func TestMathRender(t *testing.T) {
+ setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseInlineDollar: true, ParseInlineParentheses: true}
testcases := []struct {
testcase string
expected string
@@ -69,7 +72,7 @@ func TestMathRender(t *testing.T) {
},
{
"$$a$$",
- `<code class="language-math display">a</code>` + nl,
+ `<p><code class="language-math">a</code></p>` + nl,
},
{
"$$a$$ test",
@@ -111,6 +114,7 @@ func TestMathRender(t *testing.T) {
}
func TestMathRenderBlockIndent(t *testing.T) {
+ setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseBlockDollar: true, ParseBlockSquareBrackets: true}
testcases := []struct {
name string
testcase string
@@ -243,3 +247,64 @@ x
})
}
}
+
+func TestMathRenderOptions(t *testing.T) {
+ setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{}
+ defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions)
+ test := func(t *testing.T, expected, input string) {
+ res, err := RenderString(markup.NewTestRenderContext(), input)
+ assert.NoError(t, err)
+ assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(res)), "input: %s", input)
+ }
+
+ // default (non-conflict) inline syntax
+ test(t, `<p><code class="language-math">a</code></p>`, "$`a`$")
+
+ // ParseInlineDollar
+ test(t, `<p>$a$</p>`, `$a$`)
+ setting.Markdown.MathCodeBlockOptions.ParseInlineDollar = true
+ test(t, `<p><code class="language-math">a</code></p>`, `$a$`)
+
+ // ParseInlineParentheses
+ test(t, `<p>(a)</p>`, `\(a\)`)
+ setting.Markdown.MathCodeBlockOptions.ParseInlineParentheses = true
+ test(t, `<p><code class="language-math">a</code></p>`, `\(a\)`)
+
+ // ParseBlockDollar
+ test(t, `<p>$$
+a
+$$</p>
+`, `
+$$
+a
+$$
+`)
+ setting.Markdown.MathCodeBlockOptions.ParseBlockDollar = true
+ test(t, `<pre class="code-block is-loading"><code class="language-math display">
+a
+</code></pre>
+`, `
+$$
+a
+$$
+`)
+
+ // ParseBlockSquareBrackets
+ test(t, `<p>[
+a
+]</p>
+`, `
+\[
+a
+\]
+`)
+ setting.Markdown.MathCodeBlockOptions.ParseBlockSquareBrackets = true
+ test(t, `<pre class="code-block is-loading"><code class="language-math display">
+a
+</code></pre>
+`, `
+\[
+a
+\]
+`)
+}
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index 7a09be8665..4eb01bcc2d 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -47,7 +47,7 @@ func TestRender_StandardLinks(t *testing.T) {
func TestRender_Images(t *testing.T) {
setting.AppURL = AppURL
- test := func(input, expected string) {
+ render := func(input, expected string) {
buffer, err := markdown.RenderString(markup.NewTestRenderContext(FullURL), input)
assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
@@ -59,27 +59,32 @@ func TestRender_Images(t *testing.T) {
result := util.URLJoin(FullURL, url)
// hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now
- test(
+ render(
"!["+title+"]("+url+")",
`<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"[["+title+"|"+url+"]]",
`<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"[!["+title+"]("+url+")]("+href+")",
`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"!["+title+"]("+url+")",
`<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"[["+title+"|"+url+"]]",
`<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`)
- test(
+ render(
"[!["+title+"]("+url+")]("+href+")",
`<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
+
+ defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, false)()
+ render(
+ "<a><img src='a.jpg'></a>", // by the way, empty "a" tag will be removed
+ `<p dir="auto"><img src="http://localhost:3000/user13/repo11/a.jpg" loading="lazy"/></p>`)
}
func TestTotal_RenderString(t *testing.T) {
@@ -223,7 +228,7 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
<dd>This is another definition of the second term.</dd>
</dl>
<h3 id="user-content-footnotes">Footnotes</h3>
-<p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1</a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2</a></sup></p>
+<p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1 </a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2 </a></sup></p>
<div>
<hr/>
<ol>
@@ -252,7 +257,7 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
return username == "r-lyeh"
},
})
- for i := 0; i < len(sameCases); i++ {
+ for i := range sameCases {
line, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), sameCases[i])
assert.NoError(t, err)
assert.Equal(t, testAnswers[i], string(line))
@@ -308,12 +313,12 @@ func TestRenderSiblingImages_Issue12925(t *testing.T) {
testcase := `![image1](/image1)
![image2](/image2)
`
- expected := `<p><a href="/image1" target="_blank" rel="nofollow noopener"><img src="/image1" alt="image1"></a>
-<a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"></a></p>
+ expected := `<p><a href="/image1" target="_blank" rel="nofollow noopener"><img src="/image1" alt="image1"/></a>
+<a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"/></a></p>
`
- res, err := markdown.RenderRawString(markup.NewTestRenderContext(), testcase)
+ res, err := markdown.RenderString(markup.NewTestRenderContext(), testcase)
assert.NoError(t, err)
- assert.Equal(t, expected, res)
+ assert.Equal(t, expected, string(res))
}
func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
@@ -383,18 +388,74 @@ func TestColorPreview(t *testing.T) {
}
}
-func TestTaskList(t *testing.T) {
+func TestMarkdownFrontmatter(t *testing.T) {
testcases := []struct {
- testcase string
+ name string
+ input string
expected string
}{
{
+ "MapInFrontmatter",
+ `---
+key1: val1
+key2: val2
+---
+test
+`,
+ `<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> key1, key2</summary><table>
+<thead>
+<tr>
+<th>key1</th>
+<th>key2</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>val1</td>
+<td>val2</td>
+</tr>
+</tbody>
+</table>
+</details><p>test</p>
+`,
+ },
+
+ {
+ "ListInFrontmatter",
+ `---
+- item1
+- item2
+---
+test
+`,
+ `- item1
+- item2
+
+<p>test</p>
+`,
+ },
+
+ {
+ "StringInFrontmatter",
+ `---
+anything
+---
+test
+`,
+ `anything
+
+<p>test</p>
+`,
+ },
+
+ {
// data-source-position should take into account YAML frontmatter.
+ "ListAfterFrontmatter",
`---
foo: bar
---
- [ ] task 1`,
- `<details><summary><i class="icon table"></i></summary><table>
+ `<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> foo</summary><table>
<thead>
<tr>
<th>foo</th>
@@ -414,9 +475,9 @@ foo: bar
}
for _, test := range testcases {
- res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
- assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
- assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
+ res, err := markdown.RenderString(markup.NewTestRenderContext(), test.input)
+ assert.NoError(t, err, "Unexpected error in testcase: %q", test.name)
+ assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.name)
}
}
@@ -473,3 +534,16 @@ space</p>
assert.NoError(t, err)
assert.Equal(t, expected, string(result))
}
+
+func TestMarkdownLink(t *testing.T) {
+ defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
+ input := `<a href=foo>link1</a>
+<a href='/foo'>link2</a>
+<a href="#foo">link3</a>`
+ result, err := markdown.RenderString(markup.NewTestRenderContext("/base", localMetas), input)
+ assert.NoError(t, err)
+ assert.Equal(t, `<p><a href="/base/foo" rel="nofollow">link1</a>
+<a href="/base/foo" rel="nofollow">link2</a>
+<a href="#user-content-foo" rel="nofollow">link3</a></p>
+`, string(result))
+}
diff --git a/modules/markup/markdown/math/block_renderer.go b/modules/markup/markdown/math/block_renderer.go
index 412e4d0dee..95a336a02c 100644
--- a/modules/markup/markdown/math/block_renderer.go
+++ b/modules/markup/markdown/math/block_renderer.go
@@ -42,7 +42,7 @@ func (r *BlockRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node) {
l := n.Lines().Len()
- for i := 0; i < l; i++ {
+ for i := range l {
line := n.Lines().At(i)
_, _ = w.Write(util.EscapeHTML(line.Value(source)))
}
@@ -51,8 +51,8 @@ func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node)
func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
n := node.(*Block)
if entering {
- code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">`
- _ = r.renderInternal.FormatWithSafeAttrs(w, template.HTML(code))
+ codeHTML := giteaUtil.Iif[template.HTML](n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">`
+ _, _ = w.WriteString(string(r.renderInternal.ProtectSafeAttrs(codeHTML)))
r.writeLines(w, source, n)
} else {
_, _ = w.WriteString(`</code>` + giteaUtil.Iif(n.Inline, "", `</pre>`) + "\n")
diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go
index a57abe9f9b..a711d1e1cd 100644
--- a/modules/markup/markdown/math/inline_parser.go
+++ b/modules/markup/markdown/math/inline_parser.go
@@ -15,26 +15,26 @@ type inlineParser struct {
trigger []byte
endBytesSingleDollar []byte
endBytesDoubleDollar []byte
- endBytesBracket []byte
+ endBytesParentheses []byte
+ enableInlineDollar bool
}
-var defaultInlineDollarParser = &inlineParser{
- trigger: []byte{'$'},
- endBytesSingleDollar: []byte{'$'},
- endBytesDoubleDollar: []byte{'$', '$'},
-}
-
-func NewInlineDollarParser() parser.InlineParser {
- return defaultInlineDollarParser
+func NewInlineDollarParser(enableInlineDollar bool) parser.InlineParser {
+ return &inlineParser{
+ trigger: []byte{'$'},
+ endBytesSingleDollar: []byte{'$'},
+ endBytesDoubleDollar: []byte{'$', '$'},
+ enableInlineDollar: enableInlineDollar,
+ }
}
-var defaultInlineBracketParser = &inlineParser{
- trigger: []byte{'\\', '('},
- endBytesBracket: []byte{'\\', ')'},
+var defaultInlineParenthesesParser = &inlineParser{
+ trigger: []byte{'\\', '('},
+ endBytesParentheses: []byte{'\\', ')'},
}
-func NewInlineBracketParser() parser.InlineParser {
- return defaultInlineBracketParser
+func NewInlineParenthesesParser() parser.InlineParser {
+ return defaultInlineParenthesesParser
}
// Trigger triggers this parser on $ or \
@@ -46,7 +46,7 @@ func isPunctuation(b byte) bool {
return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
}
-func isBracket(b byte) bool {
+func isParenthesesClose(b byte) bool {
return b == ')'
}
@@ -70,10 +70,11 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
startMarkLen = 1
stopMark = parser.endBytesSingleDollar
if len(line) > 1 {
- if line[1] == '$' {
+ switch line[1] {
+ case '$':
startMarkLen = 2
stopMark = parser.endBytesDoubleDollar
- } else if line[1] == '`' {
+ case '`':
pos := 1
for ; pos < len(line) && line[pos] == '`'; pos++ {
}
@@ -85,7 +86,11 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
}
} else {
startMarkLen = 2
- stopMark = parser.endBytesBracket
+ stopMark = parser.endBytesParentheses
+ }
+
+ if line[0] == '$' && !parser.enableInlineDollar && (len(line) == 1 || line[1] != '`') {
+ return nil
}
if checkSurrounding {
@@ -109,7 +114,7 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
succeedingCharacter = line[i+len(stopMark)]
}
// check valid ending character
- isValidEndingChar := isPunctuation(succeedingCharacter) || isBracket(succeedingCharacter) ||
+ isValidEndingChar := isPunctuation(succeedingCharacter) || isParenthesesClose(succeedingCharacter) ||
succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0
if checkSurrounding && !isValidEndingChar {
break
@@ -121,9 +126,10 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
i++
continue
}
- if line[i] == '{' {
+ switch line[i] {
+ case '{':
depth++
- } else if line[i] == '}' {
+ case '}':
depth--
}
}
diff --git a/modules/markup/markdown/math/inline_renderer.go b/modules/markup/markdown/math/inline_renderer.go
index d000a7b317..eeeb60cc7e 100644
--- a/modules/markup/markdown/math/inline_renderer.go
+++ b/modules/markup/markdown/math/inline_renderer.go
@@ -28,7 +28,7 @@ func NewInlineRenderer(renderInternal *internal.RenderInternal) renderer.NodeRen
func (r *InlineRenderer) renderInline(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
- _ = r.renderInternal.FormatWithSafeAttrs(w, `<code class="language-math">`)
+ _, _ = w.WriteString(string(r.renderInternal.ProtectSafeAttrs(`<code class="language-math">`)))
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
segment := c.(*ast.Text).Segment
value := util.EscapeHTML(segment.Value(source))
diff --git a/modules/markup/markdown/math/math.go b/modules/markup/markdown/math/math.go
index a6ff593d62..4b74db2d76 100644
--- a/modules/markup/markdown/math/math.go
+++ b/modules/markup/markdown/math/math.go
@@ -14,10 +14,11 @@ import (
)
type Options struct {
- Enabled bool
- ParseDollarInline bool
- ParseDollarBlock bool
- ParseSquareBlock bool
+ Enabled bool
+ ParseInlineDollar bool // inline $$ xxx $$ text
+ ParseInlineParentheses bool // inline \( xxx \) text
+ ParseBlockDollar bool // block $$ multiple-line $$ text
+ ParseBlockSquareBrackets bool // block \[ multiple-line \] text
}
// Extension is a math extension
@@ -42,16 +43,16 @@ func (e *Extension) Extend(m goldmark.Markdown) {
return
}
- inlines := []util.PrioritizedValue{util.Prioritized(NewInlineBracketParser(), 501)}
- if e.options.ParseDollarInline {
- inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 502))
+ var inlines []util.PrioritizedValue
+ if e.options.ParseInlineParentheses {
+ inlines = append(inlines, util.Prioritized(NewInlineParenthesesParser(), 501))
}
- m.Parser().AddOptions(parser.WithInlineParsers(inlines...))
+ inlines = append(inlines, util.Prioritized(NewInlineDollarParser(e.options.ParseInlineDollar), 502))
+ m.Parser().AddOptions(parser.WithInlineParsers(inlines...))
m.Parser().AddOptions(parser.WithBlockParsers(
- util.Prioritized(NewBlockParser(e.options.ParseDollarBlock, e.options.ParseSquareBlock), 701),
+ util.Prioritized(NewBlockParser(e.options.ParseBlockDollar, e.options.ParseBlockSquareBrackets), 701),
))
-
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewBlockRenderer(e.renderInternal), 501),
util.Prioritized(NewInlineRenderer(e.renderInternal), 502),
diff --git a/modules/markup/markdown/meta_test.go b/modules/markup/markdown/meta_test.go
index 278c33f1d2..283d289d48 100644
--- a/modules/markup/markdown/meta_test.go
+++ b/modules/markup/markdown/meta_test.go
@@ -51,7 +51,7 @@ func TestExtractMetadata(t *testing.T) {
var meta IssueTemplate
body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
assert.NoError(t, err)
- assert.Equal(t, "", body)
+ assert.Empty(t, body)
assert.Equal(t, metaTest, meta)
assert.True(t, meta.Valid())
})
@@ -60,7 +60,7 @@ func TestExtractMetadata(t *testing.T) {
func TestExtractMetadataBytes(t *testing.T) {
t.Run("ValidFrontAndBody", func(t *testing.T) {
var meta IssueTemplate
- body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta)
+ body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta)
assert.NoError(t, err)
assert.Equal(t, bodyTest, string(body))
assert.Equal(t, metaTest, meta)
@@ -69,21 +69,21 @@ func TestExtractMetadataBytes(t *testing.T) {
t.Run("NoFirstSeparator", func(t *testing.T) {
var meta IssueTemplate
- _, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest)), &meta)
+ _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta)
assert.Error(t, err)
})
t.Run("NoLastSeparator", func(t *testing.T) {
var meta IssueTemplate
- _, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest)), &meta)
+ _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta)
assert.Error(t, err)
})
t.Run("NoBody", func(t *testing.T) {
var meta IssueTemplate
- body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta)
+ body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, sepTest), &meta)
assert.NoError(t, err)
- assert.Equal(t, "", string(body))
+ assert.Empty(t, string(body))
assert.Equal(t, metaTest, meta)
assert.True(t, meta.Valid())
})
diff --git a/modules/markup/markdown/renderconfig.go b/modules/markup/markdown/renderconfig.go
index f4c48d1b3d..d8b1b10ce6 100644
--- a/modules/markup/markdown/renderconfig.go
+++ b/modules/markup/markdown/renderconfig.go
@@ -16,7 +16,6 @@ import (
// RenderConfig represents rendering configuration for this file
type RenderConfig struct {
Meta markup.RenderMetaMode
- Icon string
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
Lang string
yamlNode *yaml.Node
@@ -74,7 +73,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
type yamlRenderConfig struct {
Meta *string `yaml:"meta"`
- Icon *string `yaml:"details_icon"`
+ Icon *string `yaml:"details_icon"` // deprecated, because there is no font icon, so no custom icon
TOC *string `yaml:"include_toc"`
Lang *string `yaml:"lang"`
}
@@ -96,10 +95,6 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta)
}
- if cfg.Gitea.Icon != nil {
- rc.Icon = strings.TrimSpace(strings.ToLower(*cfg.Gitea.Icon))
- }
-
if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" {
rc.Lang = *cfg.Gitea.Lang
}
@@ -111,7 +106,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error {
return nil
}
-func (rc *RenderConfig) toMetaNode() ast.Node {
+func (rc *RenderConfig) toMetaNode(g *ASTTransformer) ast.Node {
if rc.yamlNode == nil {
return nil
}
@@ -119,7 +114,7 @@ func (rc *RenderConfig) toMetaNode() ast.Node {
case markup.RenderMetaAsTable:
return nodeToTable(rc.yamlNode)
case markup.RenderMetaAsDetails:
- return nodeToDetails(rc.yamlNode, rc.Icon)
+ return nodeToDetails(g, rc.yamlNode)
default:
return nil
}
diff --git a/modules/markup/markdown/renderconfig_test.go b/modules/markup/markdown/renderconfig_test.go
index 13346570fa..53c52177a7 100644
--- a/modules/markup/markdown/renderconfig_test.go
+++ b/modules/markup/markdown/renderconfig_test.go
@@ -21,42 +21,36 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
{
"empty", &RenderConfig{
Meta: "table",
- Icon: "table",
Lang: "",
}, "",
},
{
"lang", &RenderConfig{
Meta: "table",
- Icon: "table",
Lang: "test",
}, "lang: test",
},
{
"metatable", &RenderConfig{
Meta: "table",
- Icon: "table",
Lang: "",
}, "gitea: table",
},
{
"metanone", &RenderConfig{
Meta: "none",
- Icon: "table",
Lang: "",
}, "gitea: none",
},
{
"metadetails", &RenderConfig{
Meta: "details",
- Icon: "table",
Lang: "",
}, "gitea: details",
},
{
"metawrong", &RenderConfig{
Meta: "details",
- Icon: "table",
Lang: "",
}, "gitea: wrong",
},
@@ -64,7 +58,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
"toc", &RenderConfig{
TOC: "true",
Meta: "table",
- Icon: "table",
Lang: "",
}, "include_toc: true",
},
@@ -72,14 +65,12 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
"tocfalse", &RenderConfig{
TOC: "false",
Meta: "table",
- Icon: "table",
Lang: "",
}, "include_toc: false",
},
{
"toclang", &RenderConfig{
Meta: "table",
- Icon: "table",
TOC: "true",
Lang: "testlang",
}, `
@@ -90,7 +81,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
{
"complexlang", &RenderConfig{
Meta: "table",
- Icon: "table",
Lang: "testlang",
}, `
gitea:
@@ -100,7 +90,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
{
"complexlang2", &RenderConfig{
Meta: "table",
- Icon: "table",
Lang: "testlang",
}, `
lang: notright
@@ -111,7 +100,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
{
"complexlang", &RenderConfig{
Meta: "table",
- Icon: "table",
Lang: "testlang",
}, `
gitea:
@@ -123,7 +111,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
Lang: "two",
Meta: "table",
TOC: "true",
- Icon: "smiley",
}, `
lang: one
include_toc: true
@@ -139,14 +126,12 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got := &RenderConfig{
Meta: "table",
- Icon: "table",
Lang: "",
}
err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got)
require.NoError(t, err)
assert.Equal(t, tt.expected.Meta, got.Meta)
- assert.Equal(t, tt.expected.Icon, got.Icon)
assert.Equal(t, tt.expected.Lang, got.Lang)
assert.Equal(t, tt.expected.TOC, got.TOC)
})
diff --git a/modules/markup/markdown/toc.go b/modules/markup/markdown/toc.go
index ea1af83a3e..a11b9d0390 100644
--- a/modules/markup/markdown/toc.go
+++ b/modules/markup/markdown/toc.go
@@ -4,7 +4,6 @@
package markdown
import (
- "fmt"
"net/url"
"code.gitea.io/gitea/modules/translation"
@@ -50,7 +49,7 @@ func createTOCNode(toc []Header, lang string, detailsAttrs map[string]string) as
}
li := ast.NewListItem(currentLevel * 2)
a := ast.NewLink()
- a.Destination = []byte(fmt.Sprintf("#%s", url.QueryEscape(header.ID)))
+ a.Destination = []byte("#" + url.QueryEscape(header.ID))
a.AppendChild(a, ast.NewString([]byte(header.Text)))
li.AppendChild(li, a)
ul.AppendChild(ul, li)
diff --git a/modules/markup/markdown/transform_blockquote.go b/modules/markup/markdown/transform_blockquote.go
index 2651d44a69..bf17f01681 100644
--- a/modules/markup/markdown/transform_blockquote.go
+++ b/modules/markup/markdown/transform_blockquote.go
@@ -46,7 +46,7 @@ func (g *ASTTransformer) extractBlockquoteAttentionEmphasis(firstParagraph ast.N
if !ok {
return "", nil
}
- val1 := string(node1.Text(reader.Source())) //nolint:staticcheck
+ val1 := string(node1.Text(reader.Source())) //nolint:staticcheck // Text is deprecated
attentionType := strings.ToLower(val1)
if g.attentionTypes.Contains(attentionType) {
return attentionType, []ast.Node{node1}
@@ -115,6 +115,9 @@ func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Read
// grab these nodes and make sure we adhere to the attention blockquote structure
firstParagraph := v.FirstChild()
+ if firstParagraph == nil {
+ return ast.WalkContinue, nil
+ }
g.applyElementDir(firstParagraph)
attentionType, processedNodes := g.extractBlockquoteAttentionEmphasis(firstParagraph, reader)
diff --git a/modules/markup/markdown/transform_codespan.go b/modules/markup/markdown/transform_codespan.go
index bccc43aad2..c2e4295bc2 100644
--- a/modules/markup/markdown/transform_codespan.go
+++ b/modules/markup/markdown/transform_codespan.go
@@ -68,7 +68,7 @@ func cssColorHandler(value string) bool {
}
func (g *ASTTransformer) transformCodeSpan(_ *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) {
- colorContent := v.Text(reader.Source()) //nolint:staticcheck
+ colorContent := v.Text(reader.Source()) //nolint:staticcheck // Text is deprecated
if cssColorHandler(string(colorContent)) {
v.AppendChild(v, NewColorPreview(colorContent))
}
diff --git a/modules/markup/markdown/transform_heading.go b/modules/markup/markdown/transform_heading.go
index 5f8a12794d..a229a7b1a4 100644
--- a/modules/markup/markdown/transform_heading.go
+++ b/modules/markup/markdown/transform_heading.go
@@ -16,10 +16,10 @@ import (
func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]Header) {
for _, attr := range v.Attributes() {
if _, ok := attr.Value.([]byte); !ok {
- v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value)))
+ v.SetAttribute(attr.Name, fmt.Appendf(nil, "%v", attr.Value))
}
}
- txt := v.Text(reader.Source()) //nolint:staticcheck
+ txt := v.Text(reader.Source()) //nolint:staticcheck // Text is deprecated
header := Header{
Text: util.UnsafeBytesToString(txt),
Level: v.Level,
diff --git a/modules/markup/markdown/transform_image.go b/modules/markup/markdown/transform_image.go
deleted file mode 100644
index 36512e59a8..0000000000
--- a/modules/markup/markdown/transform_image.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2024 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package markdown
-
-import (
- "code.gitea.io/gitea/modules/markup"
-
- "github.com/yuin/goldmark/ast"
-)
-
-func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image) {
- // Images need two things:
- //
- // 1. Their src needs to munged to be a real value
- // 2. If they're not wrapped with a link they need a link wrapper
-
- // Check if the destination is a real link
- if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) {
- v.Destination = []byte(ctx.RenderHelper.ResolveLink(string(v.Destination), markup.LinkTypeMedia))
- }
-
- parent := v.Parent()
- // Create a link around image only if parent is not already a link
- if _, ok := parent.(*ast.Link); !ok && parent != nil {
- next := v.NextSibling()
-
- // Create a link wrapper
- wrap := ast.NewLink()
- wrap.Destination = v.Destination
- wrap.Title = v.Title
- wrap.SetAttributeString("target", []byte("_blank"))
-
- // Duplicate the current image node
- image := ast.NewImage(ast.NewLink())
- image.Destination = v.Destination
- image.Title = v.Title
- for _, attr := range v.Attributes() {
- image.SetAttribute(attr.Name, attr.Value)
- }
- for child := v.FirstChild(); child != nil; {
- next := child.NextSibling()
- image.AppendChild(image, child)
- child = next
- }
-
- // Append our duplicate image to the wrapper link
- wrap.AppendChild(wrap, image)
-
- // Wire in the next sibling
- wrap.SetNextSibling(next)
-
- // Replace the current node with the wrapper link
- parent.ReplaceChild(parent, v, wrap)
-
- // But most importantly ensure the next sibling is still on the old image too
- v.SetNextSibling(next)
- }
-}
diff --git a/modules/markup/markdown/transform_link.go b/modules/markup/markdown/transform_link.go
deleted file mode 100644
index 51c2c915d8..0000000000
--- a/modules/markup/markdown/transform_link.go
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2024 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package markdown
-
-import (
- "code.gitea.io/gitea/modules/markup"
-
- "github.com/yuin/goldmark/ast"
-)
-
-func resolveLink(ctx *markup.RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) {
- isAnchorFragment := link != "" && link[0] == '#'
- if !isAnchorFragment && !markup.IsFullURLString(link) {
- link, resolved = ctx.RenderHelper.ResolveLink(link, markup.LinkTypeDefault), true
- }
- if isAnchorFragment && userContentAnchorPrefix != "" {
- link, resolved = userContentAnchorPrefix+link[1:], true
- }
- return link, resolved
-}
-
-func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link) {
- if link, resolved := resolveLink(ctx, string(v.Destination), "#user-content-"); resolved {
- v.Destination = []byte(link)
- }
-}