From ab9bcd6a79750cadff71308b33c15cf6ac0da1cf Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 8 Oct 2026 09:47:17 -0400 Subject: [PATCH] fix(markdown): parse escaped alerts by default (#39633) Previously, recognition of escaped alert markers such as `\[!NOTE\]` accidentally depended on enabling the optional `inline-parentheses` math setting, resulting in such escaped alerts not rendering properly. Goldmark splits the escaped marker differently based on the setting being enabled or disabled, and only one form was handled. This commit handles both forms. For example, in a README or issue: ``` > \[!NOTE\] > text ``` Before this commit, we'd get an ordinary blockquote displaying literal `[!NOTE]` followed by `text`. With this commit, we get the styled admonition. Co-authored-by: wxiaoguang --- .../markup/markdown/markdown_attention_test.go | 15 +++++++++++++-- modules/markup/markdown/markdown_math_test.go | 7 +++---- modules/markup/markdown/transform_blockquote.go | 7 ++++--- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/modules/markup/markdown/markdown_attention_test.go b/modules/markup/markdown/markdown_attention_test.go index d39c87f12c0..c25862ae175 100644 --- a/modules/markup/markdown/markdown_attention_test.go +++ b/modules/markup/markdown/markdown_attention_test.go @@ -9,7 +9,9 @@ import ( "gitea.dev/modules/markup" "gitea.dev/modules/markup/markdown" + "gitea.dev/modules/setting" "gitea.dev/modules/svg" + "gitea.dev/modules/test" "github.com/stretchr/testify/assert" "golang.org/x/text/cases" @@ -22,6 +24,7 @@ func TestAttention(t *testing.T) { defer svg.MockIcon("octicon-report")() defer svg.MockIcon("octicon-alert")() defer svg.MockIcon("octicon-stop")() + defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions, setting.MarkdownMathCodeBlockOptions{})() test := func(input, expected string) { result, err := markdown.RenderString(markup.NewTestRenderContext(), input) @@ -47,8 +50,16 @@ func TestAttention(t *testing.T) { test(`> [!warning]`, renderAttention("warning", "octicon-alert")+"\n") test(`> [!caution]`, renderAttention("caution", "octicon-stop")+"\n") - // escaped by mdformat - test(`> \[!NOTE\]`, renderAttention("note", "octicon-info")+"\n") + for _, parseInlineParentheses := range []bool{false, true} { + setting.Markdown.MathCodeBlockOptions.ParseInlineParentheses = parseInlineParentheses + test("> \\[!NOTE\\]\n> text", renderAttention("note", "octicon-info")+"\n

text

\n") + test(`> \[!UNKNOWN\]`, "
\n

[!UNKNOWN]

\n
") + test(`> \[NOTE\]`, "
\n

[NOTE]

\n
") + test(`> \[!NOTE`, "
\n

[!NOTE

\n
") + test(`> [!NOTE\]`, "
\n

[!NOTE]

\n
") + test("> \\[!\n> NOTE\\]", "
\n

[!\nNOTE]

\n
") + test("> \\[\n> !NOTE\\]", "
\n

[\n!NOTE]

\n
") + } // legacy GitHub style test(`> **warning**`, renderAttention("warning", "octicon-alert")+"\n") diff --git a/modules/markup/markdown/markdown_math_test.go b/modules/markup/markdown/markdown_math_test.go index 08100742476..c96ab208db1 100644 --- a/modules/markup/markdown/markdown_math_test.go +++ b/modules/markup/markdown/markdown_math_test.go @@ -17,7 +17,7 @@ import ( const nl = "\n" func TestMathRender(t *testing.T) { - setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseInlineDollar: true, ParseInlineParentheses: true} + defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions, setting.MarkdownMathCodeBlockOptions{ParseInlineDollar: true, ParseInlineParentheses: true})() testcases := []struct { testcase string expected string @@ -122,7 +122,7 @@ func TestMathRender(t *testing.T) { } func TestMathRenderBlockIndent(t *testing.T) { - setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseBlockDollar: true, ParseBlockSquareBrackets: true} + defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions, setting.MarkdownMathCodeBlockOptions{ParseBlockDollar: true, ParseBlockSquareBrackets: true})() testcases := []struct { name string testcase string @@ -257,8 +257,7 @@ x } func TestMathRenderOptions(t *testing.T) { - setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{} - defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions) + defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions, setting.MarkdownMathCodeBlockOptions{})() test := func(t *testing.T, expected, input string) { res, err := RenderString(markup.NewTestRenderContext(), input) assert.NoError(t, err) diff --git a/modules/markup/markdown/transform_blockquote.go b/modules/markup/markdown/transform_blockquote.go index ce39d4acfae..a8f0e528391 100644 --- a/modules/markup/markdown/transform_blockquote.go +++ b/modules/markup/markdown/transform_blockquote.go @@ -62,7 +62,7 @@ func (g *ASTTransformer) extractBlockquoteAttention2(firstParagraph ast.Node, re return "", nil } node1, ok := firstParagraph.FirstChild().(*ast.Text) - if !ok { + if !ok || node1.SoftLineBreak() || node1.HardLineBreak() { return "", nil } node2, ok := node1.NextSibling().(*ast.Text) @@ -71,8 +71,9 @@ func (g *ASTTransformer) extractBlockquoteAttention2(firstParagraph ast.Node, re } val1 := string(node1.Segment.Value(reader.Source())) val2 := string(node2.Segment.Value(reader.Source())) - if strings.HasPrefix(val1, `\[!`) && val2 == `\]` { - attentionType := strings.ToLower(val1[3:]) + // Inline math options change where Goldmark splits the escaped marker. + if inner, ok := strings.CutPrefix(val1+val2, `\[!`); ok && strings.HasSuffix(val2, `\]`) { + attentionType := strings.ToLower(strings.TrimSuffix(inner, `\]`)) if g.attentionTypes.Contains(attentionType) { return attentionType, []ast.Node{node1, node2} }