0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-04-03 21:12:09 +02:00

Merge 77eeee70a57431023d42e5e5f2b5b37856807cc3 into 6eed75af248ae597d854a3c5e6b8831a5ff76290

This commit is contained in:
silverwind 2026-04-02 23:42:01 -05:00 committed by GitHub
commit e23ef7cd78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 119 additions and 2 deletions

View File

@ -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

View File

@ -125,6 +125,24 @@ func TestConsistencyUpdateAction(t *testing.T) {
unittest.CheckConsistencyFor(t, &activities_model.Action{})
}
func TestGetIssueContentBody(t *testing.T) {
tests := []struct {
content string
expected string
}{
{content: "1|simple body", expected: "simple body"},
{content: "1|A -->|text| B", expected: "A -->|text| B"},
{content: "1|first|second|third", expected: "first|second|third"},
{content: "1|", expected: ""},
{content: "no-delimiter", expected: ""},
{content: "", expected: ""},
}
for _, test := range tests {
action := &activities_model.Action{Content: test.content}
assert.Equal(t, test.expected, action.GetIssueContentBody(), "content: %q", test.content)
}
}
func TestDeleteIssueActions(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())

View File

@ -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,9 @@ func (a *actionNotifier) CreateIssueComment(ctx context.Context, doer *user_mode
truncatedContent = truncatedContent[:lastSpaceIdx] + "…"
}
}
if truncatedRight != "" {
truncatedContent = trimUnclosedCodeBlock(truncatedContent)
}
act.Content = fmt.Sprintf("%d|%s", issue.Index, truncatedContent)
if issue.IsPull {

View File

@ -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())

View File

@ -107,8 +107,8 @@
{{else if .GetOpType.InActions "create_pull_request"}}
<span class="tw-inline-block tw-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="tw-inline-block tw-truncate issue title">{{(.GetIssueTitle ctx) | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
{{$comment := index .GetIssueInfos 1}}
<a href="{{.GetCommentLink ctx}}" class="text truncate issue title">{{(.GetIssueTitle ctx) | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
{{$comment := .GetIssueContentBody}}
{{if $comment}}
<div class="render-content markup truncated-markup">{{ctx.RenderUtils.MarkdownToHtml $comment}}</div>
{{end}}