diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index b700addad4..7c872c295a 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -128,7 +128,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.contactsService = contacts_service.newService(statusFoundation.events, statusFoundation.threadpool) result.chatService = chat_service.newService(statusFoundation.events, result.contactsService) 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, statusFoundation.threadpool, result.chatService) result.tokenService = token_service.newService(statusFoundation.events, statusFoundation.threadpool, diff --git a/src/app/modules/main/activity_center/controller.nim b/src/app/modules/main/activity_center/controller.nim index 7bce1bdb16..e144a24890 100644 --- a/src/app/modules/main/activity_center/controller.nim +++ b/src/app/modules/main/activity_center/controller.nim @@ -7,6 +7,7 @@ import ../../../core/signals/types import ../../../../app_service/service/activity_center/service as activity_center_service import ../../../../app_service/service/contacts/service as contacts_service import ../../../../app_service/service/chat/service as chat_service +import ../../../../app_service/service/message/service as message_service export controller_interface @@ -16,18 +17,21 @@ type events: EventEmitter activityCenterService: activity_center_service.Service contactsService: contacts_service.Service + messageService: message_service.Service proc newController*[T]( delegate: io_interface.AccessInterface, events: EventEmitter, activityCenterService: activity_center_service.Service, - contactsService: contacts_service.Service + contactsService: contacts_service.Service, + messageService: message_service.Service ): Controller[T] = result = Controller[T]() result.delegate = delegate result.events = events result.activityCenterService = activityCenterService result.contactsService = contactsService + result.messageService = messageService method delete*[T](self: Controller[T]) = discard @@ -102,4 +106,7 @@ method acceptActivityCenterNotifications*[T](self: Controller[T], notificationId return self.activityCenterService.acceptActivityCenterNotifications(notificationIds) method dismissActivityCenterNotifications*[T](self: Controller[T], notificationIds: seq[string]): string = - return self.activityCenterService.dismissActivityCenterNotifications(notificationIds) \ No newline at end of file + return self.activityCenterService.dismissActivityCenterNotifications(notificationIds) + +method getRenderedText*(self: Controller, parsedTextArray: seq[ParsedText]): string = + return self.messageService.getRenderedText(parsedTextArray) \ No newline at end of file diff --git a/src/app/modules/main/activity_center/controller_interface.nim b/src/app/modules/main/activity_center/controller_interface.nim index 71fbd89422..440e303ea3 100644 --- a/src/app/modules/main/activity_center/controller_interface.nim +++ b/src/app/modules/main/activity_center/controller_interface.nim @@ -1,5 +1,6 @@ import ../../../../app_service/service/contacts/service as contacts_service import ../../../../app_service/service/activity_center/service as activity_center_service +import ../../../../app_service/service/message/dto/[message] type 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.} = raise newException(ValueError, "No implementation available") +method getRenderedText*(self: AccessInterface, parsedTextArray: seq[ParsedText]): string {.base.} = + raise newException(ValueError, "No implementation available") + + type ## Abstract class (concept) which must be implemented by object/s used in this ## module. diff --git a/src/app/modules/main/activity_center/module.nim b/src/app/modules/main/activity_center/module.nim index 01e43406be..89e5e660a4 100644 --- a/src/app/modules/main/activity_center/module.nim +++ b/src/app/modules/main/activity_center/module.nim @@ -8,6 +8,7 @@ import ../../../global/global_singleton import ../../../core/eventemitter import ../../../../app_service/service/activity_center/service as activity_center_service import ../../../../app_service/service/contacts/service as contacts_service +import ../../../../app_service/service/message/service as message_service export io_interface @@ -23,7 +24,8 @@ proc newModule*[T]( delegate: T, events: EventEmitter, activityCenterService: activity_center_service.Service, - contactsService: contacts_service.Service + contactsService: contacts_service.Service, + messageService: message_service.Service ): Module[T] = result = Module[T]() result.delegate = delegate @@ -33,7 +35,8 @@ proc newModule*[T]( result, events, activityCenterService, - contactsService + contactsService, + messageService ) result.moduleLoaded = false @@ -78,8 +81,9 @@ method convertToItems*[T]( contactDetails.isIdenticon, contactDetails.isCurrentUser, n.message.outgoingStatus, - n.message.text, + self.controller.getRenderedText(n.message.parsedText), n.message.image, + n.message.containsContactMentions(), n.message.seen, n.message.timestamp, ContentType(n.message.contentType), diff --git a/src/app/modules/main/chat_section/chat_content/controller.nim b/src/app/modules/main/chat_section/chat_content/controller.nim index 57e7e86b57..1ea7d7b62b 100644 --- a/src/app/modules/main/chat_section/chat_content/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/controller.nim @@ -149,4 +149,7 @@ method getContactDetails*(self: Controller, contactId: string): ContactDetails = return self.contactService.getContactDetails(contactId) method getCurrentFleet*(self: Controller): string = - return self.settingsService.getFleetAsString() \ No newline at end of file + return self.settingsService.getFleetAsString() + +method getRenderedText*(self: Controller, parsedTextArray: seq[ParsedText]): string = + return self.messageService.getRenderedText(parsedTextArray) \ No newline at end of file diff --git a/src/app/modules/main/chat_section/chat_content/controller_interface.nim b/src/app/modules/main/chat_section/chat_content/controller_interface.nim index 670f361275..0a9f28e691 100644 --- a/src/app/modules/main/chat_section/chat_content/controller_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/controller_interface.nim @@ -65,4 +65,7 @@ method getContactDetails*(self: AccessInterface, contactId: string): ContactDeta raise newException(ValueError, "No implementation available") method getCurrentFleet*(self: AccessInterface): string {.base.} = + raise newException(ValueError, "No implementation available") + +method getRenderedText*(self: AccessInterface, parsedTextArray: seq[ParsedText]): string {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/chat_section/chat_content/messages/controller.nim b/src/app/modules/main/chat_section/chat_content/messages/controller.nim index f42ab9cb94..2f3308604a 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/controller.nim @@ -139,4 +139,11 @@ method getContactDetails*(self: Controller, contactId: string): ContactDetails = return self.contactService.getContactDetails(contactId) method getNumOfPinnedMessages*(self: Controller): int = - return self.messageService.getNumOfPinnedMessages(self.chatId) \ No newline at end of file + 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) \ No newline at end of file diff --git a/src/app/modules/main/chat_section/chat_content/messages/controller_interface.nim b/src/app/modules/main/chat_section/chat_content/messages/controller_interface.nim index 6be3b2bb8b..151b3d59fa 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/controller_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/controller_interface.nim @@ -1,6 +1,7 @@ import ../../../../../../app_service/service/contacts/dto/[contacts, contact_details] import ../../../../../../app_service/service/community/dto/[community] import ../../../../../../app_service/service/chat/dto/[chat] +import ../../../../../../app_service/service/message/dto/[message, reaction] type AccessInterface* {.pure inheritable.} = ref object of RootObj @@ -50,4 +51,11 @@ method getContactDetails*(self: AccessInterface, contactId: string): ContactDeta raise newException(ValueError, "No implementation available") method getNumOfPinnedMessages*(self: AccessInterface): int {.base.} = + 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") \ No newline at end of file diff --git a/src/app/modules/main/chat_section/chat_content/messages/module.nim b/src/app/modules/main/chat_section/chat_content/messages/module.nim index bbf5608412..3ac033d782 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/module.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/module.nim @@ -73,7 +73,7 @@ proc createChatIdentifierItem(self: Module): Item = (chatName, chatIcon, isIdenticon) = self.controller.getOneToOneChatNameAndImage() 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], pinnedMessages: seq[PinnedMessageDto]) = @@ -83,9 +83,10 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se for m in messages: 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, - sender.isIdenticon, sender.isCurrentUser, m.outgoingStatus, m.text, m.image, m.seen, m.timestamp, - m.contentType.ContentType, m.messageType) + sender.isIdenticon, sender.isCurrentUser, m.outgoingStatus, renderedMessageText, m.image, + m.containsContactMentions(), m.seen, m.timestamp, m.contentType.ContentType, m.messageType) for r in reactions: if(r.messageId == m.id): @@ -121,9 +122,10 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se method messageAdded*(self: Module, message: MessageDto) = 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, - sender.icon, sender.isIdenticon, sender.isCurrentUser, message.outgoingStatus, message.text, message.image, - message.seen, message.timestamp, message.contentType.ContentType, message.messageType) + sender.icon, sender.isIdenticon, sender.isCurrentUser, message.outgoingStatus, renderedMessageText, message.image, + message.containsContactMentions(), message.seen, message.timestamp, message.contentType.ContentType, message.messageType) self.view.model().insertItemBasedOnTimestamp(item) @@ -199,5 +201,16 @@ method getNumberOfPinnedMessages*(self: Module): int = method updateContactDetails*(self: Module, contactId: string) = let updatedContact = self.controller.getContactDetails(contactId) - self.view.model().updateSenderDetails(contactId, updatedContact.displayName, updatedContact.details.localNickname, - updatedContact.icon, updatedContact.isIdenticon) \ No newline at end of file + for item in self.view.model().modelContactUpdateIterator(contactId): + 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() + + diff --git a/src/app/modules/main/chat_section/chat_content/module.nim b/src/app/modules/main/chat_section/chat_content/module.nim index 3f195b3254..6aa8542118 100644 --- a/src/app/modules/main/chat_section/chat_content/module.nim +++ b/src/app/modules/main/chat_section/chat_content/module.nim @@ -142,8 +142,9 @@ proc buildPinnedMessageItem(self: Module, messageId: string, actionInitiatedBy: contactDetails.isIdenticon, contactDetails.isCurrentUser, m.outgoingStatus, - m.text, + self.controller.getRenderedText(m.parsedText), m.image, + m.containsContactMentions(), m.seen, m.timestamp, m.contentType.ContentType, @@ -250,8 +251,18 @@ method amIChatAdmin*(self: Module): bool = method onContactDetailsUpdated*(self: Module, contactId: string) = let updatedContact = self.controller.getContactDetails(contactId) - self.view.pinnedModel().updateSenderDetails(contactId, updatedContact.displayName, updatedContact.details.localNickname, - updatedContact.icon, updatedContact.isIdenticon) + for item in self.view.pinnedModel().modelContactUpdateIterator(contactId): + 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): self.view.updateChatDetailsNameAndIcon(updatedContact.displayName, updatedContact.icon, updatedContact.isIdenticon) diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 6b6e853e7b..83ffd2fc21 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -125,7 +125,8 @@ proc newModule*[T]( profileService, contactsService, aboutService, languageService, privacyService, nodeConfigurationService, devicesService, mailserversService, chatService) 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.appSearchModule = app_search_module.newModule(result, events, contactsService, chatService, communityService, messageService) diff --git a/src/app/modules/shared_models/message_item.nim b/src/app/modules/shared_models/message_item.nim index 734699df02..9cc854f891 100644 --- a/src/app/modules/shared_models/message_item.nim +++ b/src/app/modules/shared_models/message_item.nim @@ -18,6 +18,7 @@ type outgoingStatus: string messageText: string messageImage: string + messageContainsMentions: bool stickerHash: string stickerPack: int gapFrom: int64 @@ -30,8 +31,8 @@ type pinnedBy: string proc initItem*(id, responseToMessageWithId, senderId, senderDisplayName, senderLocalName, senderIcon: string, - isSenderIconIdenticon, amISender: bool, outgoingStatus, text, image: string, seen: bool, timestamp: int64, - contentType: ContentType, messageType: int): Item = + isSenderIconIdenticon, amISender: bool, outgoingStatus, text, image: string, messageContainsMentions, seen: bool, + timestamp: int64, contentType: ContentType, messageType: int): Item = result = Item() result.id = id result.responseToMessageWithId = responseToMessageWithId @@ -45,6 +46,7 @@ proc initItem*(id, responseToMessageWithId, senderId, senderDisplayName, senderL result.outgoingStatus = outgoingStatus result.messageText = text result.messageImage = image + result.messageContainsMentions = messageContainsMentions result.timestamp = timestamp result.contentType = contentType result.messageType = messageType @@ -63,6 +65,7 @@ proc `$`*(self: Item): string = seen: {$self.seen}, outgoingStatus:{$self.outgoingStatus}, messageText:{self.messageText}, + messageContainsMentions:{self.messageContainsMentions}, timestamp:{$self.timestamp}, contentType:{$self.contentType.int}, messageType:{$self.messageType}, @@ -113,9 +116,18 @@ proc outgoingStatus*(self: Item): string {.inline.} = proc messageText*(self: Item): string {.inline.} = self.messageText +proc `messageText=`*(self: Item, value: string) {.inline.} = + self.messageText = value + proc messageImage*(self: Item): string {.inline.} = 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.} = self.stickerPack @@ -176,6 +188,7 @@ proc toJsonNode*(self: Item): JsonNode = "outgoingStatus": self.outgoingStatus, "messageText": self.messageText, "messageImage": self.messageImage, + "messageContainsMentions": self.messageContainsMentions, "stickerHash": self.stickerHash, "stickerPack": self.stickerPack, "gapFrom": self.gapFrom, diff --git a/src/app/modules/shared_models/message_model.nim b/src/app/modules/shared_models/message_model.nim index 6071026c67..a13b9c95fc 100644 --- a/src/app/modules/shared_models/message_model.nim +++ b/src/app/modules/shared_models/message_model.nim @@ -16,6 +16,8 @@ type OutgoingStatus MessageText 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 ContentType MessageType @@ -73,6 +75,7 @@ QtObject: ModelRole.OutgoingStatus.int:"outgoingStatus", ModelRole.MessageText.int:"messageText", ModelRole.MessageImage.int:"messageImage", + ModelRole.MessageContainsMentions.int:"messageContainsMentions", ModelRole.Timestamp.int:"timestamp", ModelRole.ContentType.int:"contentType", ModelRole.MessageType.int:"messageType", @@ -120,6 +123,8 @@ QtObject: result = newQVariant(item.messageText) of ModelRole.MessageImage: result = newQVariant(item.messageImage) + of ModelRole.MessageContainsMentions: + result = newQVariant(item.messageContainsMentions) of ModelRole.Timestamp: result = newQVariant(item.timestamp) of ModelRole.ContentType: @@ -272,18 +277,18 @@ QtObject: return 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: + yield self.items[i] + var roles: seq[int] 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, ModelRole.IsSenderIconIdenticon.int] if(self.items[i].pinnedBy == contactId): roles.add(ModelRole.PinnedBy.int) + if(self.items[i].messageContainsMentions): + roles.add(@[ModelRole.MessageText.int, ModelRole.MessageContainsMentions.int]) if(roles.len > 0): let index = self.createIndex(i, 0, nil) diff --git a/src/app_service/service/message/dto/message.nim b/src/app_service/service/message/dto/message.nim index c0cfbc2886..cfcbe7b61a 100644 --- a/src/app_service/service/message/dto/message.nim +++ b/src/app_service/service/message/dto/message.nim @@ -4,10 +4,32 @@ import json 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 `from`*: string text*: string - #parsedText*: Not sure if we use it + parsedText*: seq[ParsedText] type Sticker* = object hash*: string @@ -27,7 +49,7 @@ type MessageDto* = object outgoingStatus*: string quotedMessage*: QuotedMessage rtl*: bool - #parsedText*: Not sure if we use it + parsedText*: seq[ParsedText] lineCount*: int text*: string chatId*: string @@ -44,10 +66,31 @@ type MessageDto* = object messageType*: int 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 = result = QuotedMessage() discard jsonObj.getProp("from", result.from) 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 = result = Sticker() @@ -97,4 +140,16 @@ proc toMessageDto*(jsonObj: JsonNode): MessageDto = var linksArr: JsonNode if(jsonObj.getProp("links", linksArr)): for link in linksArr: - result.links.add(link.getStr) \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/app_service/service/message/service.nim b/src/app_service/service/message/service.nim index f56e7c10b8..8c3b0cf90f 100644 --- a/src/app_service/service/message/service.nim +++ b/src/app_service/service/message/service.nim @@ -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/signals/types import ../../../app/core/eventemitter import status/statusgo_backend_new/messages as status_go - +import ../contacts/service as contact_service import ./dto/message as message_dto import ./dto/pinned_message as pinned_msg_dto import ./dto/reaction as reaction_dto @@ -17,6 +17,7 @@ export reaction_dto logScope: topics = "messages-service" +let NEW_LINE = re"\n|\r" #must be defined as let, not const const MESSAGES_PER_PAGE = 20 const CURSOR_VALUE_IGNORE = "ignore" @@ -66,6 +67,7 @@ QtObject: type Service* = ref object of QObject events: EventEmitter threadpool: ThreadPool + contactService: contact_service.Service msgCursor: Table[string, string] lastUsedMsgCursor: Table[string, string] pinnedMsgCursor: Table[string, string] @@ -75,11 +77,12 @@ QtObject: proc delete*(self: Service) = self.QObject.delete - proc newService*(events: EventEmitter, threadpool: ThreadPool): Service = + proc newService*(events: EventEmitter, threadpool: ThreadPool, contactService: contact_service.Service): Service = new(result, delete) result.QObject.setup result.events = events result.threadpool = threadpool + result.contactService = contactService result.msgCursor = initTable[string, string]() result.lastUsedMsgCursor = initTable[string, string]() result.pinnedMsgCursor = initTable[string, string]() @@ -481,4 +484,62 @@ QtObject: self.threadpool.start(arg) proc getNumOfPinnedMessages*(self: Service, chatId: string): int = - return self.numOfPinnedMessagesPerChat[chatId] \ No newline at end of file + 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", "
")) + .multiReplace(("\n", "
")) + .multiReplace((" ", "  ")) + + case parsedTextChild.type: + of "": + result = value + of PARSED_TEXT_CHILD_TYPE_CODE: + result = fmt("{value}") + of PARSED_TEXT_CHILD_TYPE_EMPH: + result = fmt("{value}") + of PARSED_TEXT_CHILD_TYPE_STRONG: + result = fmt("{value}") + of PARSED_TEXT_CHILD_TYPE_STRONG_EMPH: + result = fmt(" {value} ") + of PARSED_TEXT_CHILD_TYPE_MENTION: + let contactDto = self.contactService.getContactById(value) + result = fmt("{contactDto.userNameOrAlias()}") + of PARSED_TEXT_CHILD_TYPE_STATUS_TAG: + result = fmt("#{value}") + of PARSED_TEXT_CHILD_TYPE_DEL: + result = fmt("{value}") + 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 & "

" + for child in parsedText.children: + result = result & self.renderInline(child) + result = result & "

" + 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] & "
" + blockquote = lines.join("") + result = result & fmt( + "" & + "" & + "" & + "" & + "" & + "
{blockquote}
") + of PARSED_TEXT_TYPE_CODEBLOCK: + result = result & "" & escape_html(parsedText.literal) & "" + result = result.strip() \ No newline at end of file diff --git a/ui/imports/shared/views/chat/ChatTextView.qml b/ui/imports/shared/views/chat/ChatTextView.qml index fbb5a6ff3b..4a901fa0ea 100644 --- a/ui/imports/shared/views/chat/ChatTextView.qml +++ b/ui/imports/shared/views/chat/ChatTextView.qml @@ -52,8 +52,9 @@ Item { height: root.veryLongChatText && !root.readMore ? Math.min(implicitHeight, 200) : implicitHeight clip: height < implicitHeight onLinkActivated: { + root.linkActivated(link) + // Not Refactored Yet -// root.linkActivated(link) // if(link.startsWith("#")) { // const channelName = link.substring(1); // const foundChannelObj = root.store.chatsModelInst.getChannel(channelName); @@ -90,19 +91,20 @@ Item { // return // } -// if (link.startsWith('//')) { -// let pk = link.replace("//", ""); -// Global.openProfilePopup(pk) -// return; -// } + if (link.startsWith('//')) { + let pk = link.replace("//", ""); + Global.openProfilePopup(pk) + return; + } + // Not Refactored Yet // const data = Utils.getLinkDataForStatusLinks(link) // if (data && data.callback) { // return data.callback() // } -// Global.openLink(link) + Global.openLink(link) } onLinkHovered: {