diff --git a/modules/markup/html.go b/modules/markup/html.go index cef643bf18..5ae0cc8755 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -591,17 +591,16 @@ func replaceContentList(node *html.Node, i, j int, newNodes []*html.Node) { func mentionProcessor(ctx *RenderContext, node *html.Node) { start := 0 - next := node.NextSibling - for node != nil && node != next && start < len(node.Data) { - // We replace only the first mention; other mentions will be addressed later - found, loc := references.FindFirstMentionBytes([]byte(node.Data[start:])) + for node != nil { + found, loc := references.FindFirstMentionBytes(util.UnsafeStringToBytes(node.Data[start:])) if !found { - return + node = node.NextSibling + start = 0 + continue } loc.Start += start loc.End += start mention := node.Data[loc.Start:loc.End] - var teams string teams, ok := ctx.Metas["teams"] // FIXME: util.URLJoin may not be necessary here: // - setting.AppURL is defined to have a terminal '/' so unless mention[1:] @@ -623,10 +622,10 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) { replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(ctx.Links.Prefix(), mentionedUsername), mention, "mention")) node = node.NextSibling.NextSibling + start = 0 } else { - node = node.NextSibling + start = loc.End } - start = 0 } } diff --git a/modules/references/references.go b/modules/references/references.go index 761d6ee3d1..1b656ed4cb 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -29,7 +29,7 @@ var ( // TODO: fix invalid linking issue // mentionPattern matches all mentions in the form of "@user" or "@org/team" - mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_]+\/?[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+\/?[0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`) + mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[-\w][-.\w]*?|@[-\w][-.\w]*?/[-\w][-.\w]*?)(?:\s|$|[:,;.?!](\s|$)|'|\)|\])`) // issueNumericPattern matches string that references to a numeric issue, e.g. #1287 issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`) // issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234 diff --git a/modules/references/references_test.go b/modules/references/references_test.go index 0c32933619..e5a0d60fe3 100644 --- a/modules/references/references_test.go +++ b/modules/references/references_test.go @@ -392,6 +392,7 @@ func TestRegExp_mentionPattern(t *testing.T) { {"@gitea,", "@gitea"}, {"@gitea;", "@gitea"}, {"@gitea/team1;", "@gitea/team1"}, + {"@user's idea", "@user"}, } falseTestCases := []string{ "@ 0", @@ -412,7 +413,6 @@ func TestRegExp_mentionPattern(t *testing.T) { for _, testCase := range trueTestCases { found := mentionPattern.FindStringSubmatch(testCase.pat) - assert.Len(t, found, 2) assert.Equal(t, testCase.exp, found[1]) } for _, testCase := range falseTestCases { diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 47c5da6485..f493b899e3 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -207,3 +207,8 @@ func TestRenderLabels(t *testing.T) { expected = `/owner/repo/pulls?labels=123` assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected) } + +func TestUserMention(t *testing.T) { + rendered := RenderMarkdownToHtml(context.Background(), "@no-such-user @mention-user @mention-user") + assert.EqualValues(t, `
@no-such-user @mention-user @mention-user
`, strings.TrimSpace(string(rendered))) +}