mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-06 16:03:53 +02:00
Fix Mermaid code blocks broken on dashboard feed (#36582)
Fix two bugs causing broken rendering of fenced code blocks (e.g. Mermaid diagrams) in dashboard feed comments: 1. Truncation at 200 chars could cut mid-code-block, leaving an unclosed fence that produces a parse error. Add trimUnclosedCodeBlock() to strip the partial block after truncation. 2. The action content format "index|body" was parsed with SplitN(..., 3), splitting the comment body at its first "|" character. Mermaid syntax commonly uses "|" (e.g. "A -->|text| B"). Add GetIssueContentBody() which splits only on the first "|" and use it in the feed template. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
514f322dcf
commit
a56cc4da86
@ -372,6 +372,18 @@ func (a *Action) IsIssueEvent() bool {
|
||||
return a.OpType.InActions("comment_issue", "approve_pull_request", "reject_pull_request", "comment_pull", "merge_pull_request")
|
||||
}
|
||||
|
||||
// GetIssueContentBody returns the comment body from the action content.
|
||||
// Unlike GetIssueInfos which splits on "|" into 3 parts, this only splits on
|
||||
// the first "|" to preserve any "|" characters within the comment body
|
||||
// (e.g. Mermaid diagram syntax like "A -->|text| B").
|
||||
func (a *Action) GetIssueContentBody() string {
|
||||
parts := strings.SplitN(a.Content, "|", 2)
|
||||
if len(parts) < 2 {
|
||||
return ""
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
// GetIssueInfos returns a list of associated information with the action.
|
||||
func (a *Action) GetIssueInfos() []string {
|
||||
// make sure it always returns 3 elements, because there are some access to the a[1] and a[2] without checking the length
|
||||
|
||||
@ -25,6 +25,41 @@ type actionNotifier struct {
|
||||
notify_service.NullNotifier
|
||||
}
|
||||
|
||||
// trimUnclosedCodeBlock removes a trailing unclosed fenced code block from a
|
||||
// truncated string. When content is cut at an arbitrary character limit, a
|
||||
// fenced code block (``` ...) may be left open, producing invalid Markdown that
|
||||
// the renderer tries (and fails) to process. This function detects the
|
||||
// situation and strips the partial block.
|
||||
func trimUnclosedCodeBlock(s string) string {
|
||||
inBlock := false
|
||||
lastOpenIdx := -1
|
||||
i := 0
|
||||
for i < len(s) {
|
||||
lineStart := i
|
||||
lineEnd := strings.Index(s[i:], "\n")
|
||||
var line string
|
||||
if lineEnd == -1 {
|
||||
line = s[i:]
|
||||
i = len(s)
|
||||
} else {
|
||||
line = s[i : i+lineEnd]
|
||||
i = i + lineEnd + 1
|
||||
}
|
||||
if strings.HasPrefix(strings.TrimLeft(line, " "), "```") {
|
||||
if !inBlock {
|
||||
lastOpenIdx = lineStart
|
||||
inBlock = true
|
||||
} else {
|
||||
inBlock = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if inBlock && lastOpenIdx >= 0 {
|
||||
return strings.TrimRight(s[:lastOpenIdx], " \t\n")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var _ notify_service.Notifier = &actionNotifier{}
|
||||
|
||||
func Init() error {
|
||||
@ -117,6 +152,7 @@ func (a *actionNotifier) CreateIssueComment(ctx context.Context, doer *user_mode
|
||||
truncatedContent = truncatedContent[:lastSpaceIdx] + "…"
|
||||
}
|
||||
}
|
||||
truncatedContent = trimUnclosedCodeBlock(truncatedContent)
|
||||
act.Content = fmt.Sprintf("%d|%s", issue.Index, truncatedContent)
|
||||
|
||||
if issue.IsPull {
|
||||
|
||||
@ -22,6 +22,55 @@ func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m)
|
||||
}
|
||||
|
||||
func TestTrimUnclosedCodeBlock(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no code block",
|
||||
input: "hello world",
|
||||
expected: "hello world",
|
||||
},
|
||||
{
|
||||
name: "closed code block",
|
||||
input: "before\n```go\nfmt.Println()\n```\nafter",
|
||||
expected: "before\n```go\nfmt.Println()\n```\nafter",
|
||||
},
|
||||
{
|
||||
name: "unclosed code block",
|
||||
input: "before\n```mermaid\ngraph LR\nA --> B",
|
||||
expected: "before",
|
||||
},
|
||||
{
|
||||
name: "unclosed code block with leading text",
|
||||
input: "some text here\n```\ncode line 1\ncode line 2",
|
||||
expected: "some text here",
|
||||
},
|
||||
{
|
||||
name: "closed then unclosed",
|
||||
input: "```\nblock1\n```\ntext\n```\nunclosed",
|
||||
expected: "```\nblock1\n```\ntext",
|
||||
},
|
||||
{
|
||||
name: "only unclosed fence",
|
||||
input: "```mermaid\ngraph LR",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, trimUnclosedCodeBlock(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenameRepoAction(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
||||
@ -108,7 +108,7 @@
|
||||
<span class="text truncate issue title">{{index .GetIssueInfos 1 | ctx.RenderUtils.RenderIssueSimpleTitle}}</span>
|
||||
{{else if .GetOpType.InActions "comment_issue" "approve_pull_request" "reject_pull_request" "comment_pull"}}
|
||||
<a href="{{.GetCommentLink ctx}}" class="text truncate issue title">{{(.GetIssueTitle ctx) | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||
{{$comment := index .GetIssueInfos 1}}
|
||||
{{$comment := .GetIssueContentBody}}
|
||||
{{if $comment}}
|
||||
<div class="render-content markup tw-text-14">{{ctx.RenderUtils.MarkdownToHtml $comment}}</div>
|
||||
{{end}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user