diff --git a/services/feed/action.go b/services/feed/action.go index e945e0905e..d1d899a28e 100644 --- a/services/feed/action.go +++ b/services/feed/action.go @@ -125,18 +125,9 @@ func (a *actionNotifier) CreateIssueComment(ctx context.Context, doer *user_mode Comment: comment, CommentID: comment.ID, IsPrivate: issue.Repo.IsPrivate, + Content: encodeContent(fmt.Sprintf("%d", issue.Index), abbreviatedComment(comment.Content)), } - truncatedContent, truncatedRight := util.SplitStringAtByteN(comment.Content, 200) - if truncatedRight != "" { - // in case the content is in a Latin family language, we remove the last broken word. - lastSpaceIdx := strings.LastIndex(truncatedContent, " ") - if lastSpaceIdx != -1 && (len(truncatedContent)-lastSpaceIdx < 15) { - truncatedContent = truncatedContent[:lastSpaceIdx] + "…" - } - } - act.Content = encodeContent(fmt.Sprintf("%d", issue.Index), truncatedContent) - if issue.IsPull { act.OpType = activities_model.ActionCommentPull } else { @@ -247,7 +238,7 @@ func (a *actionNotifier) PullRequestReview(ctx context.Context, pr *issues_model actions = append(actions, &activities_model.Action{ ActUserID: review.Reviewer.ID, ActUser: review.Reviewer, - Content: encodeContent(fmt.Sprintf("%d", review.Issue.Index), strings.Split(comm.Content, "\n")[0]), + Content: encodeContent(fmt.Sprintf("%d", review.Issue.Index), abbreviatedComment(comm.Content)), OpType: activities_model.ActionCommentPull, RepoID: review.Issue.RepoID, Repo: review.Issue.Repo, @@ -263,7 +254,7 @@ func (a *actionNotifier) PullRequestReview(ctx context.Context, pr *issues_model action := &activities_model.Action{ ActUserID: review.Reviewer.ID, ActUser: review.Reviewer, - Content: encodeContent(fmt.Sprintf("%d", review.Issue.Index), strings.Split(comment.Content, "\n")[0]), + Content: encodeContent(fmt.Sprintf("%d", review.Issue.Index), abbreviatedComment(comment.Content)), RepoID: review.Issue.RepoID, Repo: review.Issue.Repo, IsPrivate: review.Issue.Repo.IsPrivate, @@ -325,7 +316,7 @@ func (*actionNotifier) NotifyPullRevieweDismiss(ctx context.Context, doer *user_ ActUserID: doer.ID, ActUser: doer, OpType: activities_model.ActionPullReviewDismissed, - Content: encodeContent(fmt.Sprintf("%d", review.Issue.Index), reviewerName, comment.Content), + Content: encodeContent(fmt.Sprintf("%d", review.Issue.Index), reviewerName, abbreviatedComment(comment.Content)), RepoID: review.Issue.Repo.ID, Repo: review.Issue.Repo, IsPrivate: review.Issue.Repo.IsPrivate, @@ -491,3 +482,20 @@ func encodeContent(params ...string) string { } return string(contentEncoded) } + +// Given a comment of arbitrary-length Markdown text, create an abbreviated Markdown text appropriate for the +// activity feed. +func abbreviatedComment(comment string) string { + firstLine := strings.Split(comment, "\n")[0] + + truncatedContent, truncatedRight := util.SplitStringAtByteN(firstLine, 200) + if truncatedRight != "" { + // in case the content is in a Latin family language, we remove the last broken word. + lastSpaceIdx := strings.LastIndex(truncatedContent, " ") + if lastSpaceIdx != -1 && (len(truncatedContent)-lastSpaceIdx < 15) { + truncatedContent = truncatedContent[:lastSpaceIdx] + "…" + } + } + + return truncatedContent +} diff --git a/services/feed/action_test.go b/services/feed/action_test.go index 93ca543a1a..35b170e95c 100644 --- a/services/feed/action_test.go +++ b/services/feed/action_test.go @@ -143,3 +143,54 @@ func TestPushCommits(t *testing.T) { assert.JSONEq(t, `{"Commits":[{"Sha1":"69554a6","Message":"not signed commit","AuthorEmail":"user2@example.com","AuthorName":"User2","CommitterEmail":"user2@example.com","CommitterName":"User2","Signature":null,"Verification":null,"Timestamp":"0001-01-01T00:00:00Z"}],"HeadCommit":{"Sha1":"69554a6","Message":"","AuthorEmail":"","AuthorName":"","CommitterEmail":"","CommitterName":"","Signature":null,"Verification":null,"Timestamp":"0001-01-01T00:00:00Z"},"CompareURL":"","Len":0}`, newNotification.Content) }) } + +func TestAbbreviatedComment(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "short single line comment", + input: "This is a short comment", + expected: "This is a short comment", + }, + { + name: "empty comment", + input: "", + expected: "", + }, + { + name: "multiline comment - only first line", + input: "First line of comment\nSecond line\nThird line", + expected: "First line of comment", + }, + { + name: "before clip boundry", + input: strings.Repeat("abc ", 50), + expected: strings.Repeat("abc ", 50), + }, + { + name: "after clip boundry", + input: strings.Repeat("abc ", 51), + expected: "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc…", + }, + { + name: "byte-split would land in middle of a rune", + input: strings.Repeat("🎉", 200), + expected: "🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉…", + }, + { + name: "mermaid block", + input: "Interesting point, here's a digram with my thoughts:\n```mermaid\ngraph LR\n a -->|some text| b\n```", + expected: "Interesting point, here's a digram with my thoughts:", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := abbreviatedComment(tt.input) + assert.Equal(t, tt.expected, result, "abbreviatedComment(%q)", tt.input) + }) + } +}