fix: make mentions work with ensname, alias and nickname

Fixes #3707
This commit is contained in:
Jonathan Rainville 2022-02-15 14:45:29 -05:00 committed by Iuri Matias
parent 9ed6c6235c
commit 713e6685ca
15 changed files with 212 additions and 33 deletions

View File

@ -116,5 +116,8 @@ method getContactNameAndImage*(self: Controller, contactId: string):
tuple[name: string, image: string, isIdenticon: bool] = tuple[name: string, image: string, isIdenticon: bool] =
return self.contactService.getContactNameAndImage(contactId) return self.contactService.getContactNameAndImage(contactId)
method getContactDetails*(self: Controller, contactId: string): ContactDetails =
return self.contactService.getContactDetails(contactId)
method getStatusForContact*(self: Controller, contactId: string): StatusUpdateDto = method getStatusForContact*(self: Controller, contactId: string): StatusUpdateDto =
return self.contactService.getStatusForContactWithId(contactId) return self.contactService.getStatusForContactWithId(contactId)

View File

@ -21,6 +21,9 @@ method getContactNameAndImage*(self: AccessInterface, contactId: string):
tuple[name: string, image: string, isIdenticon: bool] {.base.} = tuple[name: string, image: string, isIdenticon: bool] {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getContactDetails*(self: AccessInterface, contactId: string): ContactDetails {.base.} =
raise newException(ValueError, "No implementation available")
method getStatusForContact*(self: AccessInterface, contactId: string): StatusUpdateDto {.base.} = method getStatusForContact*(self: AccessInterface, contactId: string): StatusUpdateDto {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -55,6 +55,9 @@ method viewDidLoad*(self: Module) =
self.view.model().addItem(initItem( self.view.model().addItem(initItem(
singletonInstance.userProfile.getPubKey(), singletonInstance.userProfile.getPubKey(),
loggedInUserDisplayName, loggedInUserDisplayName,
singletonInstance.userProfile.getEnsName(),
localNickname = "",
alias = singletonInstance.userProfile.getUsername(),
OnlineStatus.Online, OnlineStatus.Online,
singletonInstance.userProfile.getIcon(), singletonInstance.userProfile.getIcon(),
singletonInstance.userProfile.getIsIdenticon(), singletonInstance.userProfile.getIsIdenticon(),
@ -69,10 +72,21 @@ method viewDidLoad*(self: Module) =
continue continue
let (admin, joined) = self.controller.getChatMemberInfo(publicKey) let (admin, joined) = self.controller.getChatMemberInfo(publicKey)
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(publicKey) let contactDetails = self.controller.getContactDetails(publicKey)
let statusUpdateDto = self.controller.getStatusForContact(publicKey) let statusUpdateDto = self.controller.getStatusForContact(publicKey)
let status = statusUpdateDto.statusType.int.OnlineStatus let status = statusUpdateDto.statusType.int.OnlineStatus
self.view.model().addItem(initItem(publicKey, name, status, image, isidenticon, admin, joined)) self.view.model().addItem(initItem(
publicKey,
contactDetails.displayName,
contactDetails.details.name,
contactDetails.details.localNickname,
contactDetails.details.alias,
status,
contactDetails.icon,
contactDetails.isidenticon,
admin,
joined
))
self.moduleLoaded = true self.moduleLoaded = true
self.delegate.usersDidLoad() self.delegate.usersDidLoad()
@ -89,14 +103,28 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto]) =
if(self.view.model().isContactWithIdAdded(m.`from`)): if(self.view.model().isContactWithIdAdded(m.`from`)):
continue continue
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(m.`from`) let contactDetails = self.controller.getContactDetails(m.`from`)
let statusUpdateDto = self.controller.getStatusForContact(m.`from`) let statusUpdateDto = self.controller.getStatusForContact(m.`from`)
let status = statusUpdateDto.statusType.int.OnlineStatus let status = statusUpdateDto.statusType.int.OnlineStatus
self.view.model().addItem(initItem(m.`from`, name, status, image, isidenticon)) self.view.model().addItem(initItem(
m.`from`,
contactDetails.displayName,
contactDetails.details.name,
contactDetails.details.localNickname,
contactDetails.details.alias,
status,
contactDetails.icon,
contactDetails.isidenticon,
))
method contactNicknameChanged*(self: Module, publicKey: string) = method contactNicknameChanged*(self: Module, publicKey: string) =
let (name, _, _) = self.controller.getContactNameAndImage(publicKey) let contactDetails = self.controller.getContactDetails(publicKey)
self.view.model().setName(publicKey, name) self.view.model().setName(
publicKey,
contactDetails.displayName,
contactDetails.details.name,
contactDetails.details.localNickname
)
method contactsStatusUpdated*(self: Module, statusUpdates: seq[StatusUpdateDto]) = method contactsStatusUpdated*(self: Module, statusUpdates: seq[StatusUpdateDto]) =
for s in statusUpdates: for s in statusUpdates:
@ -104,8 +132,16 @@ method contactsStatusUpdated*(self: Module, statusUpdates: seq[StatusUpdateDto])
self.view.model().setOnlineStatus(s.publicKey, status) self.view.model().setOnlineStatus(s.publicKey, status)
method contactUpdated*(self: Module, publicKey: string) = method contactUpdated*(self: Module, publicKey: string) =
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(publicKey) let contactDetails = self.controller.getContactDetails(publicKey)
self.view.model().updateItem(publicKey, name, image, isIdenticon) self.view.model().updateItem(
publicKey,
contactDetails.displayName,
contactDetails.details.name,
contactDetails.details.localNickname,
contactDetails.details.alias,
contactDetails.icon,
contactDetails.isidenticon,
)
method loggedInUserImageChanged*(self: Module) = method loggedInUserImageChanged*(self: Module) =
self.view.model().setIcon(singletonInstance.userProfile.getPubKey(), singletonInstance.userProfile.getIcon(), self.view.model().setIcon(singletonInstance.userProfile.getPubKey(), singletonInstance.userProfile.getIcon(),
@ -117,14 +153,34 @@ method onChatMembersAdded*(self: Module, ids: seq[string]) =
continue continue
let (admin, joined) = self.controller.getChatMemberInfo(id) let (admin, joined) = self.controller.getChatMemberInfo(id)
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(id) let contactDetails = self.controller.getContactDetails(id)
let statusUpdateDto = self.controller.getStatusForContact(id) let statusUpdateDto = self.controller.getStatusForContact(id)
let status = statusUpdateDto.statusType.int.OnlineStatus let status = statusUpdateDto.statusType.int.OnlineStatus
self.view.model().addItem(initItem(id, name, status, image, isidenticon, admin, joined)) self.view.model().addItem(initItem(
id,
contactDetails.displayName,
contactDetails.details.name,
contactDetails.details.localNickname,
contactDetails.details.alias,
status,
contactDetails.icon,
contactDetails.isidenticon,
admin,
joined
))
method onChatMemberRemoved*(self: Module, id: string) = method onChatMemberRemoved*(self: Module, id: string) =
self.view.model().removeItemById(id) self.view.model().removeItemById(id)
method onChatMemberUpdated*(self: Module, publicKey: string, admin: bool, joined: bool) = method onChatMemberUpdated*(self: Module, publicKey: string, admin: bool, joined: bool) =
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(publicKey) let contactDetails = self.controller.getContactDetails(publicKey)
self.view.model().updateItem(publicKey, name, image, isIdenticon, admin, joined) self.view.model().updateItem(
publicKey,
contactDetails.displayName,
contactDetails.details.name,
contactDetails.details.localNickname,
contactDetails.details.alias,
contactDetails.icon,
contactDetails.isidenticon,
admin,
joined)

View File

@ -122,6 +122,9 @@ method getContactNameAndImage*(self: Controller, contactId: string):
tuple[name: string, image: string, isIdenticon: bool] = tuple[name: string, image: string, isIdenticon: bool] =
return self.contactsService.getContactNameAndImage(contactId) return self.contactsService.getContactNameAndImage(contactId)
method getContactDetails*(self: Controller, contactId: string): ContactDetails =
return self.contactsService.getContactDetails(contactId)
method isUserMemberOfCommunity*(self: Controller, communityId: string): bool = method isUserMemberOfCommunity*(self: Controller, communityId: string): bool =
return self.communityService.isUserMemberOfCommunity(communityId) return self.communityService.isUserMemberOfCommunity(communityId)

View File

@ -1,4 +1,5 @@
import ../../../../app_service/service/community/service as community_service import ../../../../app_service/service/community/service as community_service
import ../../../../app_service/service/contacts/service as contacts_service
type type
AccessInterface* {.pure inheritable.} = ref object of RootObj AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -62,6 +63,9 @@ method getContactNameAndImage*(self: AccessInterface, contactId: string):
tuple[name: string, image: string, isIdenticon: bool] {.base.} = tuple[name: string, image: string, isIdenticon: bool] {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getContactDetails*(self: AccessInterface, contactId: string): ContactDetails {.base.} =
raise newException(ValueError, "No implementation available")
type type
## Abstract class (concept) which must be implemented by object/s used in this ## Abstract class (concept) which must be implemented by object/s used in this
## module. ## module.

View File

@ -89,8 +89,17 @@ method getCommunityItem(self: Module, c: CommunityDto): SectionItem =
c.permissions.access, c.permissions.access,
c.permissions.ensOnly, c.permissions.ensOnly,
c.members.map(proc(member: Member): user_item.Item = c.members.map(proc(member: Member): user_item.Item =
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(member.id) let contactDetails = self.controller.getContactDetails(member.id)
result = user_item.initItem(member.id, name, OnlineStatus.Offline, image, isIdenticon)) result = user_item.initItem(
member.id,
contactDetails.displayName,
contactDetails.details.name,
contactDetails.details.localNickname,
contactDetails.details.alias,
OnlineStatus.Offline, # TODO get the actual status?
contactDetails.icon,
contactDetails.isidenticon,
))
) )
method setAllCommunities*(self: Module, communities: seq[CommunityDto]) = method setAllCommunities*(self: Module, communities: seq[CommunityDto]) =

View File

@ -247,6 +247,9 @@ method getContactNameAndImage*(self: Controller, contactId: string):
tuple[name: string, image: string, isIdenticon: bool] = tuple[name: string, image: string, isIdenticon: bool] =
return self.contactsService.getContactNameAndImage(contactId) return self.contactsService.getContactNameAndImage(contactId)
method getContactDetails*(self: Controller, contactId: string): ContactDetails =
return self.contactsService.getContactDetails(contactId)
method resolveENS*(self: Controller, ensName: string, uuid: string = "") = method resolveENS*(self: Controller, ensName: string, uuid: string = "") =
self.contactsService.resolveENS(ensName, uuid) self.contactsService.resolveENS(ensName, uuid)

View File

@ -1,4 +1,5 @@
import ../shared_models/section_item import ../shared_models/section_item
import ../../../app_service/service/contacts/dto/contact_details as contact_details
import ../../../app_service/service/contacts/dto/contacts as contacts_dto import ../../../app_service/service/contacts/dto/contacts as contacts_dto
import ../../../app_service/service/community/service as community_service import ../../../app_service/service/community/service as community_service
@ -44,6 +45,9 @@ method getContactNameAndImage*(self: AccessInterface, contactId: string):
tuple[name: string, image: string, isIdenticon: bool] {.base.} = tuple[name: string, image: string, isIdenticon: bool] {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getContactDetails*(self: AccessInterface, contactId: string): ContactDetails {.base.} =
raise newException(ValueError, "No implementation available")
method resolveENS*(self: AccessInterface, ensName: string, uuid: string = "") {.base.} = method resolveENS*(self: AccessInterface, ensName: string, uuid: string = "") {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -194,8 +194,17 @@ proc createCommunityItem[T](self: Module[T], c: CommunityDto): SectionItem =
c.permissions.access, c.permissions.access,
c.permissions.ensOnly, c.permissions.ensOnly,
c.members.map(proc(member: Member): user_item.Item = c.members.map(proc(member: Member): user_item.Item =
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(member.id) let contactDetails = self.controller.getContactDetails(member.id)
result = user_item.initItem(member.id, name, OnlineStatus.Offline, image, isIdenticon)), result = user_item.initItem(
member.id,
contactDetails.displayName,
contactDetails.details.name,
contactDetails.details.localNickname,
contactDetails.details.alias,
OnlineStatus.Offline,
contactDetails.icon,
contactDetails.isidenticon,
)),
c.pendingRequestsToJoin.map(x => pending_request_item.initItem( c.pendingRequestsToJoin.map(x => pending_request_item.initItem(
x.id, x.id,
x.publicKey, x.publicKey,
@ -638,8 +647,16 @@ method resolvedENS*[T](self: Module[T], publicKey: string, address: string, uuid
self.view.emitResolvedENSSignal(publicKey, address, uuid) self.view.emitResolvedENSSignal(publicKey, address, uuid)
method contactUpdated*[T](self: Module[T], publicKey: string) = method contactUpdated*[T](self: Module[T], publicKey: string) =
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(publicKey) let contactDetails = self.controller.getContactDetails(publicKey)
self.view.activeSection().updateMember(publicKey, name, image, isIdenticon) self.view.activeSection().updateMember(
publicKey,
contactDetails.displayName,
contactDetails.details.name,
contactDetails.details.localNickname,
contactDetails.details.alias,
contactDetails.icon,
contactDetails.isidenticon,
)
method calculateProfileSectionHasNotification*[T](self: Module[T]): bool = method calculateProfileSectionHasNotification*[T](self: Module[T]): bool =
return not self.controller.isMnemonicBackedUp() return not self.controller.isMnemonicBackedUp()

View File

@ -136,9 +136,12 @@ QtObject:
self: ActiveSection, self: ActiveSection,
pubkey: string, pubkey: string,
name: string, name: string,
ensName: string,
localNickname: string,
alias: string,
image: string, image: string,
isIdenticon: bool) = isIdenticon: bool) =
self.item.updateMember(pubkey, name, image, isIdenticon) self.item.updateMember(pubkey, name, ensName, localNickname, alias, image, isIdenticon)
proc pendingRequestsToJoin(self: ActiveSection): QVariant {.slot.} = proc pendingRequestsToJoin(self: ActiveSection): QVariant {.slot.} =
if (self.item.id == ""): if (self.item.id == ""):

View File

@ -189,9 +189,12 @@ proc updateMember*(
self: SectionItem, self: SectionItem,
pubkey: string, pubkey: string,
name: string, name: string,
ensName: string,
nickname: string,
alias: string,
image: string, image: string,
isIdenticon: bool) = isIdenticon: bool) =
self.membersModel.updateItem(pubkey, name, image, isIdenticon) self.membersModel.updateItem(pubkey, name, ensName, nickname, alias, image, isIdenticon)
proc pendingRequestsToJoin*(self: SectionItem): PendingRequestModel {.inline.} = proc pendingRequestsToJoin*(self: SectionItem): PendingRequestModel {.inline.} =
self.pendingRequestsToJoinModel self.pendingRequestsToJoinModel

View File

@ -12,7 +12,10 @@ type
type type
Item* = ref object Item* = ref object
id: string id: string
name: string displayName: string
ensName: string
localNickname: string
alias: string
onlineStatus: OnlineStatus onlineStatus: OnlineStatus
icon: string icon: string
isIdenticon: bool isIdenticon: bool
@ -21,7 +24,10 @@ type
proc initItem*( proc initItem*(
id: string, id: string,
name: string, displayName: string,
ensName: string,
localNickname: string,
alias: string,
onlineStatus: OnlineStatus, onlineStatus: OnlineStatus,
icon: string, icon: string,
isidenticon: bool, isidenticon: bool,
@ -30,7 +36,10 @@ proc initItem*(
): Item = ): Item =
result = Item() result = Item()
result.id = id result.id = id
result.name = name result.displayName = displayName
result.ensName = ensName
result.localNickname = localNickname
result.alias = alias
result.onlineStatus = onlineStatus result.onlineStatus = onlineStatus
result.icon = icon result.icon = icon
result.isIdenticon = isidenticon result.isIdenticon = isidenticon
@ -40,7 +49,9 @@ proc initItem*(
proc `$`*(self: Item): string = proc `$`*(self: Item): string =
result = fmt"""User Item( result = fmt"""User Item(
id: {self.id}, id: {self.id},
name: {self.name}, displayName: {self.displayName},
localNickname: {self.localNickname},
alias: {self.alias},
onlineStatus: {$self.onlineStatus.int}, onlineStatus: {$self.onlineStatus.int},
icon: {self.icon}, icon: {self.icon},
isIdenticon: {$self.isIdenticon} isIdenticon: {$self.isIdenticon}
@ -52,10 +63,28 @@ proc id*(self: Item): string {.inline.} =
self.id self.id
proc name*(self: Item): string {.inline.} = proc name*(self: Item): string {.inline.} =
self.name self.displayName
proc `name=`*(self: Item, value: string) {.inline.} = proc `name=`*(self: Item, value: string) {.inline.} =
self.name = value self.displayName = value
proc ensName*(self: Item): string {.inline.} =
self.ensName
proc `ensName=`*(self: Item, value: string) {.inline.} =
self.ensName = value
proc localNickname*(self: Item): string {.inline.} =
self.localNickname
proc `localNickname=`*(self: Item, value: string) {.inline.} =
self.localNickname = value
proc alias*(self: Item): string {.inline.} =
self.alias
proc `alias=`*(self: Item, value: string) {.inline.} =
self.alias = value
proc onlineStatus*(self: Item): OnlineStatus {.inline.} = proc onlineStatus*(self: Item): OnlineStatus {.inline.} =
self.onlineStatus self.onlineStatus

View File

@ -6,6 +6,9 @@ type
ModelRole {.pure.} = enum ModelRole {.pure.} = enum
Id = UserRole + 1 Id = UserRole + 1
Name Name
EnsName
Nickname
Alias
OnlineStatus OnlineStatus
Icon Icon
IsIdenticon IsIdenticon
@ -54,6 +57,9 @@ QtObject:
{ {
ModelRole.Id.int:"id", ModelRole.Id.int:"id",
ModelRole.Name.int:"name", ModelRole.Name.int:"name",
ModelRole.EnsName.int:"ensName",
ModelRole.Nickname.int:"nickname",
ModelRole.Alias.int:"alias",
ModelRole.OnlineStatus.int:"onlineStatus", ModelRole.OnlineStatus.int:"onlineStatus",
ModelRole.Icon.int:"icon", ModelRole.Icon.int:"icon",
ModelRole.IsIdenticon.int:"isIdenticon", ModelRole.IsIdenticon.int:"isIdenticon",
@ -76,6 +82,12 @@ QtObject:
result = newQVariant(item.id) result = newQVariant(item.id)
of ModelRole.Name: of ModelRole.Name:
result = newQVariant(item.name) result = newQVariant(item.name)
of ModelRole.EnsName:
result = newQVariant(item.ensName)
of ModelRole.Nickname:
result = newQVariant(item.localNickname)
of ModelRole.Alias:
result = newQVariant(item.alias)
of ModelRole.OnlineStatus: of ModelRole.OnlineStatus:
result = newQVariant(item.onlineStatus.int) result = newQVariant(item.onlineStatus.int)
of ModelRole.Icon: of ModelRole.Icon:
@ -127,15 +139,21 @@ QtObject:
proc isContactWithIdAdded*(self: Model, id: string): bool = proc isContactWithIdAdded*(self: Model, id: string): bool =
return self.findIndexForMessageId(id) != -1 return self.findIndexForMessageId(id) != -1
proc setName*(self: Model, id: string, name: string) = proc setName*(self: Model, id: string, name: string, ensName: string, nickname: string) =
let ind = self.findIndexForMessageId(id) let ind = self.findIndexForMessageId(id)
if(ind == -1): if(ind == -1):
return return
self.items[ind].name = name self.items[ind].name = name
self.items[ind].ensName = ensName
self.items[ind].localNickname = nickname
let index = self.createIndex(ind, 0, nil) let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.Name.int]) self.dataChanged(index, index, @[
ModelRole.Name.int,
ModelRole.EnsName.int,
ModelRole.Nickname.int,
])
proc setIcon*(self: Model, id: string, icon: string, isIdenticon: bool) = proc setIcon*(self: Model, id: string, icon: string, isIdenticon: bool) =
let ind = self.findIndexForMessageId(id) let ind = self.findIndexForMessageId(id)
@ -149,14 +167,25 @@ QtObject:
self.dataChanged(index, index, @[ModelRole.Icon.int, ModelRole.IsIdenticon.int]) self.dataChanged(index, index, @[ModelRole.Icon.int, ModelRole.IsIdenticon.int])
proc updateItem*( proc updateItem*(
self: Model, id: string, name: string, icon: string, isIdenticon: bool, self: Model,
isAdmin: bool = false, joined: bool = false id: string,
name: string,
ensName: string,
localNickname: string,
alias: string,
icon: string,
isIdenticon: bool,
isAdmin: bool = false,
joined: bool = false
) = ) =
let ind = self.findIndexForMessageId(id) let ind = self.findIndexForMessageId(id)
if(ind == -1): if(ind == -1):
return return
self.items[ind].name = name self.items[ind].name = name
self.items[ind].ensName = ensName
self.items[ind].localNickname = localNickname
self.items[ind].alias = alias
self.items[ind].icon = icon self.items[ind].icon = icon
self.items[ind].isIdenticon = isIdenticon self.items[ind].isIdenticon = isIdenticon
self.items[ind].isAdmin = isAdmin self.items[ind].isAdmin = isAdmin
@ -164,7 +193,14 @@ QtObject:
let index = self.createIndex(ind, 0, nil) let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ self.dataChanged(index, index, @[
ModelRole.Name.int, ModelRole.Icon.int, ModelRole.IsIdenticon.int, ModelRole.IsAdmin.int, ModelRole.Joined.int, ModelRole.Name.int,
ModelRole.EnsName.int,
ModelRole.Nickname.int,
ModelRole.Alias.int,
ModelRole.Icon.int,
ModelRole.IsIdenticon.int,
ModelRole.IsAdmin.int,
ModelRole.Joined.int,
]) ])
proc setOnlineStatus*(self: Model, id: string, onlineStatus: OnlineStatus) = proc setOnlineStatus*(self: Model, id: string, onlineStatus: OnlineStatus) =

View File

@ -29,6 +29,9 @@ Item {
delegate: Item { delegate: Item {
property string publicKey: model.id property string publicKey: model.id
property string name: model.name property string name: model.name
property string nickname: model.nickname
property string alias: model.alias
property string ensName: model.ensName
property string icon: model.icon property string icon: model.icon
property bool isIdenticon: model.isIdenticon property bool isIdenticon: model.isIdenticon
} }
@ -65,6 +68,9 @@ Item {
const item = { const item = {
publicKey: listItem.publicKey, publicKey: listItem.publicKey,
name: listItem.name, name: listItem.name,
nickname: listItem.nickname,
alias: listItem.alias,
ensName: listItem.ensName,
icon: listItem.icon, icon: listItem.icon,
isIdenticon: listItem.isIdenticon isIdenticon: listItem.isIdenticon
} }

View File

@ -644,7 +644,7 @@ Rectangle {
width: messageInput.width width: messageInput.width
filter: messageInputField.text filter: messageInputField.text
cursorPosition: messageInputField.cursorPosition cursorPosition: messageInputField.cursorPosition
property: ["name"] property: ["name", "nickname", "ensName", "alias"]
onItemSelected: function (item, lastAtPosition, lastCursorPosition) { onItemSelected: function (item, lastAtPosition, lastCursorPosition) {
let name = item.name.replace("@", "") let name = item.name.replace("@", "")
insertMention(name, lastAtPosition, lastCursorPosition) insertMention(name, lastAtPosition, lastCursorPosition)