fix: update mention format to use custom HTML element with usernames (#1843)

This commit is contained in:
Weijie Zhao 2025-11-21 22:29:15 +08:00 committed by GitHub
parent b3b420121d
commit cfab3ff922
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 25 additions and 40 deletions

View File

@ -210,7 +210,7 @@
</template>
<script setup lang="ts">
import {ref, reactive, computed, shallowReactive, watch, nextTick} from 'vue'
import {ref, reactive, computed, shallowReactive, watch} from 'vue'
import {useI18n} from 'vue-i18n'
import CustomTransition from '@/components/misc/CustomTransition.vue'
@ -368,13 +368,6 @@ async function addComment() {
return
}
// This makes the editor trigger its mounted function again which makes it forget every input
// it currently has in its textarea. This is a counter-hack to a hack inside of vue-easymde
// which made it impossible to detect change from the outside. Therefore the component would
// not update if new content from the outside was made available.
// See https://github.com/NikulinIlya/vue-easymde/issues/3
editorActive.value = false
nextTick(() => (editorActive.value = true))
creating.value = true
try {

View File

@ -17,7 +17,6 @@
package models
import (
"strconv"
"strings"
"code.vikunja.io/api/pkg/user"
@ -27,48 +26,41 @@ import (
)
func FindMentionedUsersInText(s *xorm.Session, text string) (users map[int64]*user.User, err error) {
userIDs := extractMentionedUserIDs(text)
if len(userIDs) == 0 {
usernames := extractMentionedUsernames(text)
if len(usernames) == 0 {
return
}
return user.GetUsersByIDs(s, userIDs)
return user.GetUsersByUsername(s, usernames, true)
}
// extractMentionedUserIDs parses HTML content and extracts user IDs from mention spans.
// It looks for <span class="mention" data-id="123"> elements and returns the user IDs.
func extractMentionedUserIDs(htmlText string) []int64 {
// extractMentionedUsernames parses HTML content and extracts usernames from mention spans.
// It looks for <mention-user data-id="username"> elements and returns the usernames.
func extractMentionedUsernames(htmlText string) []string {
doc, err := html.Parse(strings.NewReader(htmlText))
if err != nil {
return nil
}
var userIDs []int64
seen := make(map[int64]bool) // Deduplicate user IDs
usernames := []string{}
seen := make(map[string]bool) // Deduplicate usernames
var traverse func(*html.Node)
traverse = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "span" {
isMention := false
if n.Type == html.ElementNode && n.Data == "mention-user" {
var dataID string
// Check if this span has class="mention" and extract data-id
// Extract data-id attribute
for _, attr := range n.Attr {
if attr.Key == "class" && strings.Contains(attr.Val, "mention") {
isMention = true
}
if attr.Key == "data-id" {
dataID = attr.Val
}
}
// If this is a mention span with a valid data-id, extract the user ID
if isMention && dataID != "" {
if userID, err := strconv.ParseInt(dataID, 10, 64); err == nil {
if !seen[userID] {
userIDs = append(userIDs, userID)
seen[userID] = true
}
if dataID != "" {
if !seen[dataID] {
usernames = append(usernames, dataID)
seen[dataID] = true
}
}
}
@ -80,5 +72,5 @@ func extractMentionedUserIDs(htmlText string) []int64 {
}
traverse(doc)
return userIDs
return usernames
}

View File

@ -47,27 +47,27 @@ func TestFindMentionedUsersInText(t *testing.T) {
},
{
name: "one user at the beginning",
text: `<p><span class="mention" data-type="mention" data-id="1" data-label="user1">@user1</span> Lorem Ipsum</p>`,
text: `<p><mention-user data-id="user1">@user1</mention-user> Lorem Ipsum</p>`,
wantUsers: []*user.User{user1},
},
{
name: "one user at the end",
text: `<p>Lorem Ipsum <span class="mention" data-type="mention" data-id="1" data-label="user1">@user1</span></p>`,
text: `<p>Lorem Ipsum <mention-user data-id="user1">@user1</mention-user></p>`,
wantUsers: []*user.User{user1},
},
{
name: "one user in the middle",
text: `<p>Lorem <span class="mention" data-type="mention" data-id="1" data-label="user1">@user1</span> Ipsum</p>`,
text: `<p>Lorem <mention-user data-id="user1">@user1</mention-user> Ipsum</p>`,
wantUsers: []*user.User{user1},
},
{
name: "same user multiple times",
text: `<p>Lorem <span class="mention" data-type="mention" data-id="1" data-label="user1">@user1</span> Ipsum <span class="mention" data-type="mention" data-id="1" data-label="user1">@user1</span> <span class="mention" data-type="mention" data-id="1" data-label="user1">@user1</span></p>`,
text: `<p>Lorem <mention-user data-id="user1">@user1</mention-user> Ipsum <mention-user data-id="user1">@user1</mention-user> <mention-user data-id="user1">@user1</mention-user></p>`,
wantUsers: []*user.User{user1},
},
{
name: "Multiple users",
text: `<p>Lorem <span class="mention" data-type="mention" data-id="1" data-label="user1">@user1</span> Ipsum <span class="mention" data-type="mention" data-id="2" data-label="user2">@user2</span></p>`,
text: `<p>Lorem <mention-user data-id="user1">@user1</mention-user> Ipsum <mention-user data-id="user2">@user2</mention-user></p>`,
wantUsers: []*user.User{user1, user2},
},
}
@ -103,7 +103,7 @@ func TestSendingMentionNotification(t *testing.T) {
task, err := GetTaskByIDSimple(s, 32)
require.NoError(t, err)
tc := &TaskComment{
Comment: `<p>Lorem Ipsum <span class="mention" data-type="mention" data-id="1" data-label="user1">@user1</span> <span class="mention" data-type="mention" data-id="2" data-label="user2">@user2</span> <span class="mention" data-type="mention" data-id="3" data-label="user3">@user3</span> <span class="mention" data-type="mention" data-id="4" data-label="user4">@user4</span> <span class="mention" data-type="mention" data-id="5" data-label="user5">@user5</span> <span class="mention" data-type="mention" data-id="6" data-label="user6">@user6</span></p>`,
Comment: `<p>Lorem Ipsum <mention-user data-id="user1">@user1</mention-user> <mention-user data-id="user2">@user2</mention-user> <mention-user data-id="user3">@user3</mention-user> <mention-user data-id="user4">@user4</mention-user> <mention-user data-id="user5">@user5</mention-user> <mention-user data-id="user6">@user6</mention-user></p>`,
TaskID: 32, // user2 has access to the project that task belongs to
}
err = tc.Create(s, u)
@ -156,7 +156,7 @@ func TestSendingMentionNotification(t *testing.T) {
task, err := GetTaskByIDSimple(s, 32)
require.NoError(t, err)
tc := &TaskComment{
Comment: `<p>Lorem Ipsum <span class="mention" data-type="mention" data-id="2" data-label="user2">@user2</span></p>`,
Comment: `<p>Lorem Ipsum <mention-user data-id="user2">@user2</mention-user></p>`,
TaskID: 32, // user2 has access to the project that task belongs to
}
err = tc.Create(s, u)
@ -170,7 +170,7 @@ func TestSendingMentionNotification(t *testing.T) {
_, err = notifyMentionedUsers(s, &task, tc.Comment, n)
require.NoError(t, err)
_, err = notifyMentionedUsers(s, &task, `<p>Lorem Ipsum <span class="mention" data-type="mention" data-id="2" data-label="user2">@user2</span> <span class="mention" data-type="mention" data-id="3" data-label="user3">@user3</span></p>`, n)
_, err = notifyMentionedUsers(s, &task, `<p>Lorem Ipsum <mention-user data-id="user2">@user2</mention-user> <mention-user data-id="user3">@user3</mention-user></p>`, n)
require.NoError(t, err)
// The second time mentioning the user in the same task should not create another notification

View File

@ -75,7 +75,7 @@ func TestTaskComment_Create(t *testing.T) {
task, err := GetTaskByIDSimple(s, 32)
require.NoError(t, err)
tc := &TaskComment{
Comment: `<p>Lorem Ipsum <span class="mention" data-type="mention" data-id="2" data-label="user2">@user2</span></p>`,
Comment: `<p>Lorem Ipsum <mention-user data-id="user2">@user2</mention-user></p>`,
TaskID: 32, // user2 has access to the project that task belongs to
}
err = tc.Create(s, u)