refactor(@desktop/chat): mentioning a user in chat reveals his public key instead of user name

This fix also includes mention users name update according to their
local/ens names, in app runtime.

Fixes #4403
This commit is contained in:
Sale Djenic 2022-01-13 15:45:34 +01:00
parent 35568d1d6b
commit f7e8b68715
16 changed files with 239 additions and 40 deletions

View File

@ -128,7 +128,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.contactsService = contacts_service.newService(statusFoundation.events, statusFoundation.threadpool) result.contactsService = contacts_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.chatService = chat_service.newService(statusFoundation.events, result.contactsService) result.chatService = chat_service.newService(statusFoundation.events, result.contactsService)
result.communityService = community_service.newService(statusFoundation.events, result.chatService) result.communityService = community_service.newService(statusFoundation.events, result.chatService)
result.messageService = message_service.newService(statusFoundation.events, statusFoundation.threadpool) result.messageService = message_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.contactsService)
result.activityCenterService = activity_center_service.newService(statusFoundation.events, result.activityCenterService = activity_center_service.newService(statusFoundation.events,
statusFoundation.threadpool, result.chatService) statusFoundation.threadpool, result.chatService)
result.tokenService = token_service.newService(statusFoundation.events, statusFoundation.threadpool, result.tokenService = token_service.newService(statusFoundation.events, statusFoundation.threadpool,

View File

@ -7,6 +7,7 @@ import ../../../core/signals/types
import ../../../../app_service/service/activity_center/service as activity_center_service import ../../../../app_service/service/activity_center/service as activity_center_service
import ../../../../app_service/service/contacts/service as contacts_service import ../../../../app_service/service/contacts/service as contacts_service
import ../../../../app_service/service/chat/service as chat_service import ../../../../app_service/service/chat/service as chat_service
import ../../../../app_service/service/message/service as message_service
export controller_interface export controller_interface
@ -16,18 +17,21 @@ type
events: EventEmitter events: EventEmitter
activityCenterService: activity_center_service.Service activityCenterService: activity_center_service.Service
contactsService: contacts_service.Service contactsService: contacts_service.Service
messageService: message_service.Service
proc newController*[T]( proc newController*[T](
delegate: io_interface.AccessInterface, delegate: io_interface.AccessInterface,
events: EventEmitter, events: EventEmitter,
activityCenterService: activity_center_service.Service, activityCenterService: activity_center_service.Service,
contactsService: contacts_service.Service contactsService: contacts_service.Service,
messageService: message_service.Service
): Controller[T] = ): Controller[T] =
result = Controller[T]() result = Controller[T]()
result.delegate = delegate result.delegate = delegate
result.events = events result.events = events
result.activityCenterService = activityCenterService result.activityCenterService = activityCenterService
result.contactsService = contactsService result.contactsService = contactsService
result.messageService = messageService
method delete*[T](self: Controller[T]) = method delete*[T](self: Controller[T]) =
discard discard
@ -103,3 +107,6 @@ method acceptActivityCenterNotifications*[T](self: Controller[T], notificationId
method dismissActivityCenterNotifications*[T](self: Controller[T], notificationIds: seq[string]): string = method dismissActivityCenterNotifications*[T](self: Controller[T], notificationIds: seq[string]): string =
return self.activityCenterService.dismissActivityCenterNotifications(notificationIds) return self.activityCenterService.dismissActivityCenterNotifications(notificationIds)
method getRenderedText*(self: Controller, parsedTextArray: seq[ParsedText]): string =
return self.messageService.getRenderedText(parsedTextArray)

View File

@ -1,5 +1,6 @@
import ../../../../app_service/service/contacts/service as contacts_service import ../../../../app_service/service/contacts/service as contacts_service
import ../../../../app_service/service/activity_center/service as activity_center_service import ../../../../app_service/service/activity_center/service as activity_center_service
import ../../../../app_service/service/message/dto/[message]
type type
AccessInterface* {.pure inheritable.} = ref object of RootObj AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -38,6 +39,10 @@ method acceptActivityCenterNotifications*(self: AccessInterface, notificationIds
method dismissActivityCenterNotifications*(self: AccessInterface, notificationIds: seq[string]): string {.base.} = method dismissActivityCenterNotifications*(self: AccessInterface, notificationIds: seq[string]): string {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getRenderedText*(self: AccessInterface, parsedTextArray: seq[ParsedText]): string {.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

@ -8,6 +8,7 @@ import ../../../global/global_singleton
import ../../../core/eventemitter import ../../../core/eventemitter
import ../../../../app_service/service/activity_center/service as activity_center_service import ../../../../app_service/service/activity_center/service as activity_center_service
import ../../../../app_service/service/contacts/service as contacts_service import ../../../../app_service/service/contacts/service as contacts_service
import ../../../../app_service/service/message/service as message_service
export io_interface export io_interface
@ -23,7 +24,8 @@ proc newModule*[T](
delegate: T, delegate: T,
events: EventEmitter, events: EventEmitter,
activityCenterService: activity_center_service.Service, activityCenterService: activity_center_service.Service,
contactsService: contacts_service.Service contactsService: contacts_service.Service,
messageService: message_service.Service
): Module[T] = ): Module[T] =
result = Module[T]() result = Module[T]()
result.delegate = delegate result.delegate = delegate
@ -33,7 +35,8 @@ proc newModule*[T](
result, result,
events, events,
activityCenterService, activityCenterService,
contactsService contactsService,
messageService
) )
result.moduleLoaded = false result.moduleLoaded = false
@ -78,8 +81,9 @@ method convertToItems*[T](
contactDetails.isIdenticon, contactDetails.isIdenticon,
contactDetails.isCurrentUser, contactDetails.isCurrentUser,
n.message.outgoingStatus, n.message.outgoingStatus,
n.message.text, self.controller.getRenderedText(n.message.parsedText),
n.message.image, n.message.image,
n.message.containsContactMentions(),
n.message.seen, n.message.seen,
n.message.timestamp, n.message.timestamp,
ContentType(n.message.contentType), ContentType(n.message.contentType),

View File

@ -150,3 +150,6 @@ method getContactDetails*(self: Controller, contactId: string): ContactDetails =
method getCurrentFleet*(self: Controller): string = method getCurrentFleet*(self: Controller): string =
return self.settingsService.getFleetAsString() return self.settingsService.getFleetAsString()
method getRenderedText*(self: Controller, parsedTextArray: seq[ParsedText]): string =
return self.messageService.getRenderedText(parsedTextArray)

View File

@ -66,3 +66,6 @@ method getContactDetails*(self: AccessInterface, contactId: string): ContactDeta
method getCurrentFleet*(self: AccessInterface): string {.base.} = method getCurrentFleet*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getRenderedText*(self: AccessInterface, parsedTextArray: seq[ParsedText]): string {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -140,3 +140,10 @@ method getContactDetails*(self: Controller, contactId: string): ContactDetails =
method getNumOfPinnedMessages*(self: Controller): int = method getNumOfPinnedMessages*(self: Controller): int =
return self.messageService.getNumOfPinnedMessages(self.chatId) return self.messageService.getNumOfPinnedMessages(self.chatId)
method getRenderedText*(self: Controller, parsedTextArray: seq[ParsedText]): string =
return self.messageService.getRenderedText(parsedTextArray)
method getMessageDetails*(self: Controller, messageId: string):
tuple[message: MessageDto, reactions: seq[ReactionDto], error: string] =
return self.messageService.getDetailsForMessage(self.chatId, messageId)

View File

@ -1,6 +1,7 @@
import ../../../../../../app_service/service/contacts/dto/[contacts, contact_details] import ../../../../../../app_service/service/contacts/dto/[contacts, contact_details]
import ../../../../../../app_service/service/community/dto/[community] import ../../../../../../app_service/service/community/dto/[community]
import ../../../../../../app_service/service/chat/dto/[chat] import ../../../../../../app_service/service/chat/dto/[chat]
import ../../../../../../app_service/service/message/dto/[message, reaction]
type type
AccessInterface* {.pure inheritable.} = ref object of RootObj AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -51,3 +52,10 @@ method getContactDetails*(self: AccessInterface, contactId: string): ContactDeta
method getNumOfPinnedMessages*(self: AccessInterface): int {.base.} = method getNumOfPinnedMessages*(self: AccessInterface): int {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getRenderedText*(self: AccessInterface, parsedTextArray: seq[ParsedText]): string {.base.} =
raise newException(ValueError, "No implementation available")
method getMessageDetails*(self: AccessInterface, messageId: string):
tuple[message: MessageDto, reactions: seq[ReactionDto], error: string] {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -73,7 +73,7 @@ proc createChatIdentifierItem(self: Module): Item =
(chatName, chatIcon, isIdenticon) = self.controller.getOneToOneChatNameAndImage() (chatName, chatIcon, isIdenticon) = self.controller.getOneToOneChatNameAndImage()
result = initItem(CHAT_IDENTIFIER_MESSAGE_ID, "", chatDto.id, chatName, "", chatIcon, isIdenticon, false, "", "", "", result = initItem(CHAT_IDENTIFIER_MESSAGE_ID, "", chatDto.id, chatName, "", chatIcon, isIdenticon, false, "", "", "",
true, 0, ContentType.ChatIdentifier, -1) false, true, 0, ContentType.ChatIdentifier, -1)
method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: seq[ReactionDto], method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: seq[ReactionDto],
pinnedMessages: seq[PinnedMessageDto]) = pinnedMessages: seq[PinnedMessageDto]) =
@ -83,9 +83,10 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
for m in messages: for m in messages:
let sender = self.controller.getContactDetails(m.`from`) let sender = self.controller.getContactDetails(m.`from`)
let renderedMessageText = self.controller.getRenderedText(m.parsedText)
var item = initItem(m.id, m.responseTo, m.`from`, sender.displayName, sender.details.localNickname, sender.icon, var item = initItem(m.id, m.responseTo, m.`from`, sender.displayName, sender.details.localNickname, sender.icon,
sender.isIdenticon, sender.isCurrentUser, m.outgoingStatus, m.text, m.image, m.seen, m.timestamp, sender.isIdenticon, sender.isCurrentUser, m.outgoingStatus, renderedMessageText, m.image,
m.contentType.ContentType, m.messageType) m.containsContactMentions(), m.seen, m.timestamp, m.contentType.ContentType, m.messageType)
for r in reactions: for r in reactions:
if(r.messageId == m.id): if(r.messageId == m.id):
@ -121,9 +122,10 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
method messageAdded*(self: Module, message: MessageDto) = method messageAdded*(self: Module, message: MessageDto) =
let sender = self.controller.getContactDetails(message.`from`) let sender = self.controller.getContactDetails(message.`from`)
let renderedMessageText = self.controller.getRenderedText(message.parsedText)
var item = initItem(message.id, message.responseTo, message.`from`, sender.displayName, sender.details.localNickname, var item = initItem(message.id, message.responseTo, message.`from`, sender.displayName, sender.details.localNickname,
sender.icon, sender.isIdenticon, sender.isCurrentUser, message.outgoingStatus, message.text, message.image, sender.icon, sender.isIdenticon, sender.isCurrentUser, message.outgoingStatus, renderedMessageText, message.image,
message.seen, message.timestamp, message.contentType.ContentType, message.messageType) message.containsContactMentions(), message.seen, message.timestamp, message.contentType.ContentType, message.messageType)
self.view.model().insertItemBasedOnTimestamp(item) self.view.model().insertItemBasedOnTimestamp(item)
@ -199,5 +201,16 @@ method getNumberOfPinnedMessages*(self: Module): int =
method updateContactDetails*(self: Module, contactId: string) = method updateContactDetails*(self: Module, contactId: string) =
let updatedContact = self.controller.getContactDetails(contactId) let updatedContact = self.controller.getContactDetails(contactId)
self.view.model().updateSenderDetails(contactId, updatedContact.displayName, updatedContact.details.localNickname, for item in self.view.model().modelContactUpdateIterator(contactId):
updatedContact.icon, updatedContact.isIdenticon) if(item.senderId == contactId):
item.senderDisplayName = updatedContact.displayName
item.senderLocalName = updatedContact.details.localNickname
item.senderIcon = updatedContact.icon
item.isSenderIconIdenticon = updatedContact.isIdenticon
if(item.messageContainsMentions):
let (m, _, err) = self.controller.getMessageDetails(item.id)
if(err.len == 0):
item.messageText = self.controller.getRenderedText(m.parsedText)
item.messageContainsMentions = m.containsContactMentions()

View File

@ -142,8 +142,9 @@ proc buildPinnedMessageItem(self: Module, messageId: string, actionInitiatedBy:
contactDetails.isIdenticon, contactDetails.isIdenticon,
contactDetails.isCurrentUser, contactDetails.isCurrentUser,
m.outgoingStatus, m.outgoingStatus,
m.text, self.controller.getRenderedText(m.parsedText),
m.image, m.image,
m.containsContactMentions(),
m.seen, m.seen,
m.timestamp, m.timestamp,
m.contentType.ContentType, m.contentType.ContentType,
@ -250,8 +251,18 @@ method amIChatAdmin*(self: Module): bool =
method onContactDetailsUpdated*(self: Module, contactId: string) = method onContactDetailsUpdated*(self: Module, contactId: string) =
let updatedContact = self.controller.getContactDetails(contactId) let updatedContact = self.controller.getContactDetails(contactId)
self.view.pinnedModel().updateSenderDetails(contactId, updatedContact.displayName, updatedContact.details.localNickname, for item in self.view.pinnedModel().modelContactUpdateIterator(contactId):
updatedContact.icon, updatedContact.isIdenticon) if(item.senderId == contactId):
item.senderDisplayName = updatedContact.displayName
item.senderLocalName = updatedContact.details.localNickname
item.senderIcon = updatedContact.icon
item.isSenderIconIdenticon = updatedContact.isIdenticon
if(item.messageContainsMentions):
let (m, _, err) = self.controller.getMessageDetails(item.id)
if(err.len == 0):
item.messageText = self.controller.getRenderedText(m.parsedText)
item.messageContainsMentions = m.containsContactMentions()
if(self.controller.getMyChatId() == contactId): if(self.controller.getMyChatId() == contactId):
self.view.updateChatDetailsNameAndIcon(updatedContact.displayName, updatedContact.icon, updatedContact.isIdenticon) self.view.updateChatDetailsNameAndIcon(updatedContact.displayName, updatedContact.icon, updatedContact.isIdenticon)

View File

@ -125,7 +125,8 @@ proc newModule*[T](
profileService, contactsService, aboutService, languageService, privacyService, nodeConfigurationService, profileService, contactsService, aboutService, languageService, privacyService, nodeConfigurationService,
devicesService, mailserversService, chatService) devicesService, mailserversService, chatService)
result.stickersModule = stickers_module.newModule(result, events, stickersService) result.stickersModule = stickers_module.newModule(result, events, stickersService)
result.activityCenterModule = activity_center_module.newModule(result, events, activityCenterService, contactsService) result.activityCenterModule = activity_center_module.newModule(result, events, activityCenterService, contactsService,
messageService)
result.communitiesModule = communities_module.newModule(result, events, communityService) result.communitiesModule = communities_module.newModule(result, events, communityService)
result.appSearchModule = app_search_module.newModule(result, events, contactsService, chatService, communityService, result.appSearchModule = app_search_module.newModule(result, events, contactsService, chatService, communityService,
messageService) messageService)

View File

@ -18,6 +18,7 @@ type
outgoingStatus: string outgoingStatus: string
messageText: string messageText: string
messageImage: string messageImage: string
messageContainsMentions: bool
stickerHash: string stickerHash: string
stickerPack: int stickerPack: int
gapFrom: int64 gapFrom: int64
@ -30,8 +31,8 @@ type
pinnedBy: string pinnedBy: string
proc initItem*(id, responseToMessageWithId, senderId, senderDisplayName, senderLocalName, senderIcon: string, proc initItem*(id, responseToMessageWithId, senderId, senderDisplayName, senderLocalName, senderIcon: string,
isSenderIconIdenticon, amISender: bool, outgoingStatus, text, image: string, seen: bool, timestamp: int64, isSenderIconIdenticon, amISender: bool, outgoingStatus, text, image: string, messageContainsMentions, seen: bool,
contentType: ContentType, messageType: int): Item = timestamp: int64, contentType: ContentType, messageType: int): Item =
result = Item() result = Item()
result.id = id result.id = id
result.responseToMessageWithId = responseToMessageWithId result.responseToMessageWithId = responseToMessageWithId
@ -45,6 +46,7 @@ proc initItem*(id, responseToMessageWithId, senderId, senderDisplayName, senderL
result.outgoingStatus = outgoingStatus result.outgoingStatus = outgoingStatus
result.messageText = text result.messageText = text
result.messageImage = image result.messageImage = image
result.messageContainsMentions = messageContainsMentions
result.timestamp = timestamp result.timestamp = timestamp
result.contentType = contentType result.contentType = contentType
result.messageType = messageType result.messageType = messageType
@ -63,6 +65,7 @@ proc `$`*(self: Item): string =
seen: {$self.seen}, seen: {$self.seen},
outgoingStatus:{$self.outgoingStatus}, outgoingStatus:{$self.outgoingStatus},
messageText:{self.messageText}, messageText:{self.messageText},
messageContainsMentions:{self.messageContainsMentions},
timestamp:{$self.timestamp}, timestamp:{$self.timestamp},
contentType:{$self.contentType.int}, contentType:{$self.contentType.int},
messageType:{$self.messageType}, messageType:{$self.messageType},
@ -113,9 +116,18 @@ proc outgoingStatus*(self: Item): string {.inline.} =
proc messageText*(self: Item): string {.inline.} = proc messageText*(self: Item): string {.inline.} =
self.messageText self.messageText
proc `messageText=`*(self: Item, value: string) {.inline.} =
self.messageText = value
proc messageImage*(self: Item): string {.inline.} = proc messageImage*(self: Item): string {.inline.} =
self.messageImage self.messageImage
proc messageContainsMentions*(self: Item): bool {.inline.} =
self.messageContainsMentions
proc `messageContainsMentions=`*(self: Item, value: bool) {.inline.} =
self.messageContainsMentions = value
proc stickerPack*(self: Item): int {.inline.} = proc stickerPack*(self: Item): int {.inline.} =
self.stickerPack self.stickerPack
@ -176,6 +188,7 @@ proc toJsonNode*(self: Item): JsonNode =
"outgoingStatus": self.outgoingStatus, "outgoingStatus": self.outgoingStatus,
"messageText": self.messageText, "messageText": self.messageText,
"messageImage": self.messageImage, "messageImage": self.messageImage,
"messageContainsMentions": self.messageContainsMentions,
"stickerHash": self.stickerHash, "stickerHash": self.stickerHash,
"stickerPack": self.stickerPack, "stickerPack": self.stickerPack,
"gapFrom": self.gapFrom, "gapFrom": self.gapFrom,

View File

@ -16,6 +16,8 @@ type
OutgoingStatus OutgoingStatus
MessageText MessageText
MessageImage MessageImage
MessageContainsMentions # Actually we don't need to exposed this to qml since we only used it as an improved way to
# check whether we need to update mentioned contact name or not.
Timestamp Timestamp
ContentType ContentType
MessageType MessageType
@ -73,6 +75,7 @@ QtObject:
ModelRole.OutgoingStatus.int:"outgoingStatus", ModelRole.OutgoingStatus.int:"outgoingStatus",
ModelRole.MessageText.int:"messageText", ModelRole.MessageText.int:"messageText",
ModelRole.MessageImage.int:"messageImage", ModelRole.MessageImage.int:"messageImage",
ModelRole.MessageContainsMentions.int:"messageContainsMentions",
ModelRole.Timestamp.int:"timestamp", ModelRole.Timestamp.int:"timestamp",
ModelRole.ContentType.int:"contentType", ModelRole.ContentType.int:"contentType",
ModelRole.MessageType.int:"messageType", ModelRole.MessageType.int:"messageType",
@ -120,6 +123,8 @@ QtObject:
result = newQVariant(item.messageText) result = newQVariant(item.messageText)
of ModelRole.MessageImage: of ModelRole.MessageImage:
result = newQVariant(item.messageImage) result = newQVariant(item.messageImage)
of ModelRole.MessageContainsMentions:
result = newQVariant(item.messageContainsMentions)
of ModelRole.Timestamp: of ModelRole.Timestamp:
result = newQVariant(item.timestamp) result = newQVariant(item.timestamp)
of ModelRole.ContentType: of ModelRole.ContentType:
@ -272,18 +277,18 @@ QtObject:
return return
self.items[index].toJsonNode() self.items[index].toJsonNode()
proc updateSenderDetails*(self: Model, contactId, displayName, localName, icon: string, isIdenticon: bool) = iterator modelContactUpdateIterator*(self: Model, contactId: string): Item =
for i in 0 ..< self.items.len: for i in 0 ..< self.items.len:
yield self.items[i]
var roles: seq[int] var roles: seq[int]
if(self.items[i].senderId == contactId): if(self.items[i].senderId == contactId):
self.items[i].senderDisplayName = displayName
self.items[i].senderLocalName = localName
self.items[i].senderIcon = icon
self.items[i].isSenderIconIdenticon = isIdenticon
roles = @[ModelRole.SenderDisplayName.int, ModelRole.SenderLocalName.int, ModelRole.SenderIcon.int, roles = @[ModelRole.SenderDisplayName.int, ModelRole.SenderLocalName.int, ModelRole.SenderIcon.int,
ModelRole.IsSenderIconIdenticon.int] ModelRole.IsSenderIconIdenticon.int]
if(self.items[i].pinnedBy == contactId): if(self.items[i].pinnedBy == contactId):
roles.add(ModelRole.PinnedBy.int) roles.add(ModelRole.PinnedBy.int)
if(self.items[i].messageContainsMentions):
roles.add(@[ModelRole.MessageText.int, ModelRole.MessageContainsMentions.int])
if(roles.len > 0): if(roles.len > 0):
let index = self.createIndex(i, 0, nil) let index = self.createIndex(i, 0, nil)

View File

@ -4,10 +4,32 @@ import json
include ../../../common/json_utils include ../../../common/json_utils
const PARSED_TEXT_TYPE_PARAGRAPH* = "paragraph"
const PARSED_TEXT_TYPE_BLOCKQUOTE* = "blockquote"
const PARSED_TEXT_TYPE_CODEBLOCK* = "codeblock"
const PARSED_TEXT_CHILD_TYPE_CODE* = "code"
const PARSED_TEXT_CHILD_TYPE_EMPH* = "emph"
const PARSED_TEXT_CHILD_TYPE_STRONG* = "strong"
const PARSED_TEXT_CHILD_TYPE_STRONG_EMPH* = "strong-emph"
const PARSED_TEXT_CHILD_TYPE_MENTION* = "mention"
const PARSED_TEXT_CHILD_TYPE_STATUS_TAG* = "status-tag"
const PARSED_TEXT_CHILD_TYPE_DEL* = "del"
type ParsedTextChild* = object
`type`*: string
literal*: string
destination*: string
type ParsedText* = object
`type`*: string
literal*: string
children*: seq[ParsedTextChild]
type QuotedMessage* = object type QuotedMessage* = object
`from`*: string `from`*: string
text*: string text*: string
#parsedText*: Not sure if we use it parsedText*: seq[ParsedText]
type Sticker* = object type Sticker* = object
hash*: string hash*: string
@ -27,7 +49,7 @@ type MessageDto* = object
outgoingStatus*: string outgoingStatus*: string
quotedMessage*: QuotedMessage quotedMessage*: QuotedMessage
rtl*: bool rtl*: bool
#parsedText*: Not sure if we use it parsedText*: seq[ParsedText]
lineCount*: int lineCount*: int
text*: string text*: string
chatId*: string chatId*: string
@ -44,11 +66,32 @@ type MessageDto* = object
messageType*: int messageType*: int
links*: seq[string] links*: seq[string]
proc toParsedTextChild*(jsonObj: JsonNode): ParsedTextChild =
result = ParsedTextChild()
discard jsonObj.getProp("type", result.type)
discard jsonObj.getProp("literal", result.literal)
discard jsonObj.getProp("destination", result.destination)
proc toParsedText*(jsonObj: JsonNode): ParsedText =
result = ParsedText()
discard jsonObj.getProp("type", result.type)
discard jsonObj.getProp("literal", result.literal)
var childrenArr: JsonNode
if(jsonObj.getProp("children", childrenArr) and childrenArr.kind == JArray):
for childObj in childrenArr:
result.children.add(toParsedTextChild(childObj))
proc toQuotedMessage*(jsonObj: JsonNode): QuotedMessage = proc toQuotedMessage*(jsonObj: JsonNode): QuotedMessage =
result = QuotedMessage() result = QuotedMessage()
discard jsonObj.getProp("from", result.from) discard jsonObj.getProp("from", result.from)
discard jsonObj.getProp("text", result.text) discard jsonObj.getProp("text", result.text)
var parsedTextArr: JsonNode
if(jsonObj.getProp("parsedText", parsedTextArr) and parsedTextArr.kind == JArray):
for pTextObj in parsedTextArr:
result.parsedText.add(toParsedText(pTextObj))
proc toSticker*(jsonObj: JsonNode): Sticker = proc toSticker*(jsonObj: JsonNode): Sticker =
result = Sticker() result = Sticker()
discard jsonObj.getProp("hash", result.hash) discard jsonObj.getProp("hash", result.hash)
@ -98,3 +141,15 @@ proc toMessageDto*(jsonObj: JsonNode): MessageDto =
if(jsonObj.getProp("links", linksArr)): if(jsonObj.getProp("links", linksArr)):
for link in linksArr: for link in linksArr:
result.links.add(link.getStr) result.links.add(link.getStr)
var parsedTextArr: JsonNode
if(jsonObj.getProp("parsedText", parsedTextArr) and parsedTextArr.kind == JArray):
for pTextObj in parsedTextArr:
result.parsedText.add(toParsedText(pTextObj))
proc containsContactMentions*(self: MessageDto): bool =
for pText in self.parsedText:
for child in pText.children:
if (child.type == PARSED_TEXT_CHILD_TYPE_MENTION):
return true
return false

View File

@ -1,10 +1,10 @@
import NimQml, tables, json, sequtils, chronicles import NimQml, tables, json, re, sequtils, strformat, strutils, chronicles
import ../../../app/core/tasks/[qt, threadpool] import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/core/signals/types import ../../../app/core/signals/types
import ../../../app/core/eventemitter import ../../../app/core/eventemitter
import status/statusgo_backend_new/messages as status_go import status/statusgo_backend_new/messages as status_go
import ../contacts/service as contact_service
import ./dto/message as message_dto import ./dto/message as message_dto
import ./dto/pinned_message as pinned_msg_dto import ./dto/pinned_message as pinned_msg_dto
import ./dto/reaction as reaction_dto import ./dto/reaction as reaction_dto
@ -17,6 +17,7 @@ export reaction_dto
logScope: logScope:
topics = "messages-service" topics = "messages-service"
let NEW_LINE = re"\n|\r" #must be defined as let, not const
const MESSAGES_PER_PAGE = 20 const MESSAGES_PER_PAGE = 20
const CURSOR_VALUE_IGNORE = "ignore" const CURSOR_VALUE_IGNORE = "ignore"
@ -66,6 +67,7 @@ QtObject:
type Service* = ref object of QObject type Service* = ref object of QObject
events: EventEmitter events: EventEmitter
threadpool: ThreadPool threadpool: ThreadPool
contactService: contact_service.Service
msgCursor: Table[string, string] msgCursor: Table[string, string]
lastUsedMsgCursor: Table[string, string] lastUsedMsgCursor: Table[string, string]
pinnedMsgCursor: Table[string, string] pinnedMsgCursor: Table[string, string]
@ -75,11 +77,12 @@ QtObject:
proc delete*(self: Service) = proc delete*(self: Service) =
self.QObject.delete self.QObject.delete
proc newService*(events: EventEmitter, threadpool: ThreadPool): Service = proc newService*(events: EventEmitter, threadpool: ThreadPool, contactService: contact_service.Service): Service =
new(result, delete) new(result, delete)
result.QObject.setup result.QObject.setup
result.events = events result.events = events
result.threadpool = threadpool result.threadpool = threadpool
result.contactService = contactService
result.msgCursor = initTable[string, string]() result.msgCursor = initTable[string, string]()
result.lastUsedMsgCursor = initTable[string, string]() result.lastUsedMsgCursor = initTable[string, string]()
result.pinnedMsgCursor = initTable[string, string]() result.pinnedMsgCursor = initTable[string, string]()
@ -482,3 +485,61 @@ QtObject:
proc getNumOfPinnedMessages*(self: Service, chatId: string): int = proc getNumOfPinnedMessages*(self: Service, chatId: string): int =
return self.numOfPinnedMessagesPerChat[chatId] return self.numOfPinnedMessagesPerChat[chatId]
# See render-inline in status-react/src/status_im/ui/screens/chat/message/message.cljs
proc renderInline(self: Service, parsedTextChild: ParsedTextChild): string =
let value = escape_html(parsedTextChild.literal)
.multiReplace(("\r\n", "<br/>"))
.multiReplace(("\n", "<br/>"))
.multiReplace((" ", "&nbsp;&nbsp;"))
case parsedTextChild.type:
of "":
result = value
of PARSED_TEXT_CHILD_TYPE_CODE:
result = fmt("<code>{value}</code>")
of PARSED_TEXT_CHILD_TYPE_EMPH:
result = fmt("<em>{value}</em>")
of PARSED_TEXT_CHILD_TYPE_STRONG:
result = fmt("<strong>{value}</strong>")
of PARSED_TEXT_CHILD_TYPE_STRONG_EMPH:
result = fmt(" <strong><em>{value}</em></strong> ")
of PARSED_TEXT_CHILD_TYPE_MENTION:
let contactDto = self.contactService.getContactById(value)
result = fmt("<a href=\"//{value}\" class=\"mention\">{contactDto.userNameOrAlias()}</a>")
of PARSED_TEXT_CHILD_TYPE_STATUS_TAG:
result = fmt("<a href=\"#{value}\" class=\"status-tag\">#{value}</a>")
of PARSED_TEXT_CHILD_TYPE_DEL:
result = fmt("<del>{value}</del>")
else:
result = fmt(" {value} ")
# See render-block in status-react/src/status_im/ui/screens/chat/message/message.cljs
proc getRenderedText*(self: Service, parsedTextArray: seq[ParsedText]): string =
for parsedText in parsedTextArray:
case parsedText.type:
of PARSED_TEXT_TYPE_PARAGRAPH:
result = result & "<p>"
for child in parsedText.children:
result = result & self.renderInline(child)
result = result & "</p>"
of PARSED_TEXT_TYPE_BLOCKQUOTE:
var
blockquote = escape_html(parsedText.literal)
lines = toSeq(blockquote.split(NEW_LINE))
for i in 0..(lines.len - 1):
if i + 1 >= lines.len:
continue
if lines[i + 1] != "":
lines[i] = lines[i] & "<br/>"
blockquote = lines.join("")
result = result & fmt(
"<table class=\"blockquote\">" &
"<tr>" &
"<td class=\"quoteline\" valign=\"middle\"></td>" &
"<td>{blockquote}</td>" &
"</tr>" &
"</table>")
of PARSED_TEXT_TYPE_CODEBLOCK:
result = result & "<code>" & escape_html(parsedText.literal) & "</code>"
result = result.strip()

View File

@ -52,8 +52,9 @@ Item {
height: root.veryLongChatText && !root.readMore ? Math.min(implicitHeight, 200) : implicitHeight height: root.veryLongChatText && !root.readMore ? Math.min(implicitHeight, 200) : implicitHeight
clip: height < implicitHeight clip: height < implicitHeight
onLinkActivated: { onLinkActivated: {
root.linkActivated(link)
// Not Refactored Yet // Not Refactored Yet
// root.linkActivated(link)
// if(link.startsWith("#")) { // if(link.startsWith("#")) {
// const channelName = link.substring(1); // const channelName = link.substring(1);
// const foundChannelObj = root.store.chatsModelInst.getChannel(channelName); // const foundChannelObj = root.store.chatsModelInst.getChannel(channelName);
@ -90,19 +91,20 @@ Item {
// return // return
// } // }
// if (link.startsWith('//')) { if (link.startsWith('//')) {
// let pk = link.replace("//", ""); let pk = link.replace("//", "");
// Global.openProfilePopup(pk) Global.openProfilePopup(pk)
// return; return;
// } }
// Not Refactored Yet
// const data = Utils.getLinkDataForStatusLinks(link) // const data = Utils.getLinkDataForStatusLinks(link)
// if (data && data.callback) { // if (data && data.callback) {
// return data.callback() // return data.callback()
// } // }
// Global.openLink(link) Global.openLink(link)
} }
onLinkHovered: { onLinkHovered: {