mirror of
https://github.com/go-gitea/gitea
synced 2025-07-12 13:37:20 +00:00
Fix markdown render behaviors (#34122)
* Fix #27645 * Add config options `MATH_CODE_BLOCK_DETECTION`, problematic syntaxes are disabled by default * Fix #33639 * Add config options `RENDER_OPTIONS_*`, old behaviors are kept
This commit is contained in:
@ -9,7 +9,6 @@ import (
|
||||
"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"
|
||||
@ -69,16 +68,8 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
|
||||
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"]
|
||||
switch markdownLineBreakStyle {
|
||||
case "comment":
|
||||
v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments)
|
||||
case "document":
|
||||
v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments)
|
||||
}
|
||||
newLineHardBreak := ctx.RenderOptions.Metas["markdownNewLineHardBreak"] == "true"
|
||||
v.SetHardLineBreak(newLineHardBreak)
|
||||
}
|
||||
case *ast.CodeSpan:
|
||||
g.transformCodeSpan(ctx, v, reader)
|
||||
|
@ -126,11 +126,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,
|
||||
),
|
||||
|
@ -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
|
||||
\]
|
||||
`)
|
||||
}
|
||||
|
@ -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(enableInlineDollar bool) parser.InlineParser {
|
||||
return &inlineParser{
|
||||
trigger: []byte{'$'},
|
||||
endBytesSingleDollar: []byte{'$'},
|
||||
endBytesDoubleDollar: []byte{'$', '$'},
|
||||
enableInlineDollar: enableInlineDollar,
|
||||
}
|
||||
}
|
||||
|
||||
func NewInlineDollarParser() parser.InlineParser {
|
||||
return defaultInlineDollarParser
|
||||
var defaultInlineParenthesesParser = &inlineParser{
|
||||
trigger: []byte{'\\', '('},
|
||||
endBytesParentheses: []byte{'\\', ')'},
|
||||
}
|
||||
|
||||
var defaultInlineBracketParser = &inlineParser{
|
||||
trigger: []byte{'\\', '('},
|
||||
endBytesBracket: []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 == ')'
|
||||
}
|
||||
|
||||
@ -86,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 {
|
||||
@ -110,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
|
||||
|
@ -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))
|
||||
}
|
||||
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),
|
||||
|
Reference in New Issue
Block a user