feat(context-menu): add Copy message action in message context menu

Adds the action to copy the right-clicked message's text.

It copies the unparsed text (no html).
For that, I had to add it in the MessageItem and expose it in the model.

I also fixed the copy MessageId action that didn't show + didn't work even if it would have shown.

Plus some small cleanups.
This commit is contained in:
Jonathan Rainville 2023-01-06 15:19:27 -05:00 committed by Iuri Matias
parent acf67c33d5
commit af0c9767d1
10 changed files with 190 additions and 148 deletions

View File

@ -82,6 +82,7 @@ proc createMessageItemFromDto(self: Module, message: MessageDto, chatDetails: Ch
contactDetails.details.added,
message.outgoingStatus,
self.controller.getRenderedText(message.parsedText),
message.text,
message.image,
message.containsContactMentions(),
message.seen,

View File

@ -86,6 +86,7 @@ proc createFetchMoreMessagesItem(self: Module): Item =
senderIsAdded = false,
outgoingStatus = "",
text = "",
unparsedText = "",
image = "",
messageContainsMentions = false,
seen = true,
@ -134,6 +135,7 @@ proc createChatIdentifierItem(self: Module): Item =
senderIsAdded,
outgoingStatus = "",
text = "",
unparsedText = "",
image = "",
messageContainsMentions = false,
seen = true,
@ -186,68 +188,69 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
var viewItems: seq[Item]
if(messages.len > 0):
for m in messages:
for message in messages:
# https://github.com/status-im/status-desktop/issues/7632 will introduce deleteFroMe feature.
# Now we just skip deleted messages
if m.deleted or m.deletedForMe:
if message.deleted or message.deletedForMe:
continue
let sender = self.controller.getContactDetails(m.`from`)
let sender = self.controller.getContactDetails(message.`from`)
let renderedMessageText = self.controller.getRenderedText(m.parsedText)
var transactionContract = m.transactionParameters.contract
var transactionValue = m.transactionParameters.value
let renderedMessageText = self.controller.getRenderedText(message.parsedText)
var transactionContract = message.transactionParameters.contract
var transactionValue = message.transactionParameters.value
var isCurrentUser = sender.isCurrentUser
if(m.contentType.ContentType == ContentType.Transaction):
(transactionContract, transactionValue) = self.controller.getTransactionDetails(m)
if m.transactionParameters.fromAddress != "":
isCurrentUser = self.currentUserWalletContainsAddress(m.transactionParameters.fromAddress)
if(message.contentType.ContentType == ContentType.Transaction):
(transactionContract, transactionValue) = self.controller.getTransactionDetails(message)
if message.transactionParameters.fromAddress != "":
isCurrentUser = self.currentUserWalletContainsAddress(message.transactionParameters.fromAddress)
var item = initItem(
m.id,
m.communityId,
m.responseTo,
m.`from`,
message.id,
message.communityId,
message.responseTo,
message.`from`,
sender.defaultDisplayName,
sender.optionalName,
sender.icon,
(isCurrentUser and m.contentType.ContentType != ContentType.DiscordMessage),
(isCurrentUser and message.contentType.ContentType != ContentType.DiscordMessage),
sender.details.added,
m.outgoingStatus,
message.outgoingStatus,
renderedMessageText,
m.image,
m.containsContactMentions(),
m.seen,
timestamp = m.whisperTimestamp,
clock = m.clock,
m.contentType.ContentType,
m.messageType,
m.contactRequestState,
sticker = m.sticker.url,
m.sticker.pack,
m.links,
newTransactionParametersItem(m.transactionParameters.id,
m.transactionParameters.fromAddress,
m.transactionParameters.address,
message.text,
message.image,
message.containsContactMentions(),
message.seen,
timestamp = message.whisperTimestamp,
clock = message.clock,
message.contentType.ContentType,
message.messageType,
message.contactRequestState,
sticker = message.sticker.url,
message.sticker.pack,
message.links,
newTransactionParametersItem(message.transactionParameters.id,
message.transactionParameters.fromAddress,
message.transactionParameters.address,
transactionContract,
transactionValue,
m.transactionParameters.transactionHash,
m.transactionParameters.commandState,
m.transactionParameters.signature),
m.mentionedUsersPks(),
message.transactionParameters.transactionHash,
message.transactionParameters.commandState,
message.transactionParameters.signature),
message.mentionedUsersPks(),
sender.details.trustStatus,
sender.details.ensVerified,
m.discordMessage,
message.discordMessage,
resendError = "",
m.mentioned,
m.quotedMessage.`from`,
m.quotedMessage.text,
self.controller.getRenderedText(m.quotedMessage.parsedText),
m.quotedMessage.contentType,
m.quotedMessage.deleted,
message.mentioned,
message.quotedMessage.`from`,
message.quotedMessage.text,
self.controller.getRenderedText(message.quotedMessage.parsedText),
message.quotedMessage.contentType,
message.quotedMessage.deleted,
)
for r in reactions:
if(r.messageId == m.id):
if(r.messageId == message.id):
var emojiIdAsEnum: EmojiId
if(message_reaction_item.toEmojiIdAsEnum(r.emojiId, emojiIdAsEnum)):
let userWhoAddedThisReaction = self.controller.getContactById(r.`from`)
@ -258,16 +261,16 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
error "wrong emoji id found when loading messages", methodName="newMessagesLoaded"
for p in pinnedMessages:
if(p.message.id == m.id):
if(p.message.id == message.id):
item.pinned = true
item.pinnedBy = p.pinnedBy
if m.editedAt != 0:
if message.editedAt != 0:
item.isEdited = true
if(m.contentType.ContentType == ContentType.Gap):
item.gapFrom = m.gapParameters.`from`
item.gapTo = m.gapParameters.to
if(message.contentType.ContentType == ContentType.Gap):
item.gapFrom = message.gapParameters.`from`
item.gapTo = message.gapParameters.to
# messages are sorted from the most recent to the least recent one
viewItems.add(item)
@ -320,6 +323,7 @@ method messageAdded*(self: Module, message: MessageDto) =
sender.details.added,
message.outgoingStatus,
renderedMessageText,
message.text,
message.image,
message.containsContactMentions(),
message.seen,
@ -455,8 +459,8 @@ method getChatIcon*(self: Module): string =
method amIChatAdmin*(self: Module): bool =
if(not self.controller.belongsToCommunity()):
let chatDto = self.controller.getChatDetails()
for m in chatDto.members:
if (m.id == singletonInstance.userProfile.getPubKey() and m.admin):
for member in chatDto.members:
if (member.id == singletonInstance.userProfile.getPubKey() and member.admin):
return true
return false
else:
@ -483,10 +487,10 @@ method updateContactDetails*(self: Module, contactId: string) =
item.senderTrustStatus = updatedContact.details.trustStatus
item.senderEnsVerified = updatedContact.details.ensVerified
if(item.messageContainsMentions):
let (m, _, err) = self.controller.getMessageDetails(item.id)
let (message, _, err) = self.controller.getMessageDetails(item.id)
if(err.len == 0):
item.messageText = self.controller.getRenderedText(m.parsedText)
item.messageContainsMentions = m.containsContactMentions()
item.messageText = self.controller.getRenderedText(message.parsedText)
item.messageContainsMentions = message.containsContactMentions()
method deleteMessage*(self: Module, messageId: string) =
self.controller.deleteMessage(messageId)
@ -582,60 +586,61 @@ method getMessages*(self: Module): seq[message_item.Item] =
return self.view.model().items
method getMessageById*(self: Module, messageId: string): message_item.Item =
let (m, _, err) = self.controller.getMessageDetails(messageId)
let (message, _, err) = self.controller.getMessageDetails(messageId)
if(err.len == 0):
let sender = self.controller.getContactDetails(m.`from`)
let sender = self.controller.getContactDetails(message.`from`)
let renderedMessageText = self.controller.getRenderedText(m.parsedText)
var transactionContract = m.transactionParameters.contract
var transactionValue = m.transactionParameters.value
let renderedMessageText = self.controller.getRenderedText(message.parsedText)
var transactionContract = message.transactionParameters.contract
var transactionValue = message.transactionParameters.value
var isCurrentUser = sender.isCurrentUser
if(m.contentType.ContentType == ContentType.Transaction):
(transactionContract, transactionValue) = self.controller.getTransactionDetails(m)
if m.transactionParameters.fromAddress != "":
isCurrentUser = self.currentUserWalletContainsAddress(m.transactionParameters.fromAddress)
if(message.contentType.ContentType == ContentType.Transaction):
(transactionContract, transactionValue) = self.controller.getTransactionDetails(message)
if message.transactionParameters.fromAddress != "":
isCurrentUser = self.currentUserWalletContainsAddress(message.transactionParameters.fromAddress)
var item = initItem(
m.id,
m.communityId,
m.responseTo,
m.`from`,
message.id,
message.communityId,
message.responseTo,
message.`from`,
sender.defaultDisplayName,
sender.optionalName,
sender.icon,
(isCurrentUser and m.contentType.ContentType != ContentType.DiscordMessage),
(isCurrentUser and message.contentType.ContentType != ContentType.DiscordMessage),
sender.details.added,
m.outgoingStatus,
message.outgoingStatus,
renderedMessageText,
m.image,
m.containsContactMentions(),
m.seen,
timestamp = m.whisperTimestamp,
clock = m.clock,
m.contentType.ContentType,
m.messageType,
m.contactRequestState,
sticker = m.sticker.url,
m.sticker.pack,
m.links,
newTransactionParametersItem(m.transactionParameters.id,
m.transactionParameters.fromAddress,
m.transactionParameters.address,
message.text,
message.image,
message.containsContactMentions(),
message.seen,
timestamp = message.whisperTimestamp,
clock = message.clock,
message.contentType.ContentType,
message.messageType,
message.contactRequestState,
sticker = message.sticker.url,
message.sticker.pack,
message.links,
newTransactionParametersItem(message.transactionParameters.id,
message.transactionParameters.fromAddress,
message.transactionParameters.address,
transactionContract,
transactionValue,
m.transactionParameters.transactionHash,
m.transactionParameters.commandState,
m.transactionParameters.signature),
m.mentionedUsersPks(),
message.transactionParameters.transactionHash,
message.transactionParameters.commandState,
message.transactionParameters.signature),
message.mentionedUsersPks(),
sender.details.trustStatus,
sender.details.ensVerified,
m.discordMessage,
message.discordMessage,
resendError = "",
m.mentioned,
m.quotedMessage.`from`,
m.quotedMessage.text,
self.controller.getRenderedText(m.quotedMessage.parsedText),
m.quotedMessage.contentType,
m.quotedMessage.deleted,
message.mentioned,
message.quotedMessage.`from`,
message.quotedMessage.text,
self.controller.getRenderedText(message.quotedMessage.parsedText),
message.quotedMessage.contentType,
message.quotedMessage.deleted,
)
return item
return nil

View File

@ -150,67 +150,68 @@ method currentUserWalletContainsAddress(self: Module, address: string): bool =
proc buildPinnedMessageItem(self: Module, messageId: string, actionInitiatedBy: string, item: var pinned_msg_item.Item):
bool =
let (m, reactions, err) = self.controller.getMessageDetails(messageId)
let (message, reactions, err) = self.controller.getMessageDetails(messageId)
if(err.len > 0):
return false
let contactDetails = self.controller.getContactDetails(m.`from`)
let contactDetails = self.controller.getContactDetails(message.`from`)
var transactionContract = m.transactionParameters.contract
var transactionValue = m.transactionParameters.value
var transactionContract = message.transactionParameters.contract
var transactionValue = message.transactionParameters.value
var isCurrentUser = contactDetails.isCurrentUser
if(m.contentType.ContentType == ContentType.Transaction):
(transactionContract, transactionValue) = self.controller.getTransactionDetails(m)
if m.transactionParameters.fromAddress != "":
isCurrentUser = self.currentUserWalletContainsAddress(m.transactionParameters.fromAddress)
if(message.contentType.ContentType == ContentType.Transaction):
(transactionContract, transactionValue) = self.controller.getTransactionDetails(message)
if message.transactionParameters.fromAddress != "":
isCurrentUser = self.currentUserWalletContainsAddress(message.transactionParameters.fromAddress)
item = pinned_msg_item.initItem(
m.id,
m.communityId,
m.responseTo,
m.`from`,
message.id,
message.communityId,
message.responseTo,
message.`from`,
contactDetails.defaultDisplayName,
contactDetails.optionalName,
contactDetails.icon,
isCurrentUser,
contactDetails.details.added,
m.outgoingStatus,
self.controller.getRenderedText(m.parsedText),
m.image,
m.containsContactMentions(),
m.seen,
timestamp = m.whisperTimestamp,
clock = m.clock,
m.contentType.ContentType,
m.messageType,
m.contactRequestState,
m.sticker.url,
m.sticker.pack,
m.links,
newTransactionParametersItem(m.transactionParameters.id,
m.transactionParameters.fromAddress,
m.transactionParameters.address,
message.outgoingStatus,
self.controller.getRenderedText(message.parsedText),
message.text,
message.image,
message.containsContactMentions(),
message.seen,
timestamp = message.whisperTimestamp,
clock = message.clock,
message.contentType.ContentType,
message.messageType,
message.contactRequestState,
message.sticker.url,
message.sticker.pack,
message.links,
newTransactionParametersItem(message.transactionParameters.id,
message.transactionParameters.fromAddress,
message.transactionParameters.address,
transactionContract,
transactionValue,
m.transactionParameters.transactionHash,
m.transactionParameters.commandState,
m.transactionParameters.signature),
m.mentionedUsersPks,
message.transactionParameters.transactionHash,
message.transactionParameters.commandState,
message.transactionParameters.signature),
message.mentionedUsersPks,
contactDetails.details.trustStatus,
contactDetails.details.ensVerified,
m.discordMessage,
message.discordMessage,
resendError = "",
m.mentioned,
m.quotedMessage.`from`,
m.quotedMessage.text,
self.controller.getRenderedText(m.quotedMessage.parsedText),
m.quotedMessage.contentType,
m.quotedMessage.deleted,
message.mentioned,
message.quotedMessage.`from`,
message.quotedMessage.text,
self.controller.getRenderedText(message.quotedMessage.parsedText),
message.quotedMessage.contentType,
message.quotedMessage.deleted,
)
item.pinned = true
item.pinnedBy = actionInitiatedBy
for r in reactions:
if(r.messageId == m.id):
if(r.messageId == message.id):
var emojiIdAsEnum: pinned_msg_reaction_item.EmojiId
if(pinned_msg_reaction_item.toEmojiIdAsEnum(r.emojiId, emojiIdAsEnum)):
let userWhoAddedThisReaction = self.controller.getContactById(r.`from`)
@ -318,8 +319,8 @@ method getCurrentFleet*(self: Module): string =
method amIChatAdmin*(self: Module): bool =
if(not self.controller.belongsToCommunity()):
let chatDto = self.controller.getChatDetails()
for m in chatDto.members:
if (m.id == singletonInstance.userProfile.getPubKey() and m.admin):
for member in chatDto.members:
if (member.id == singletonInstance.userProfile.getPubKey() and member.admin):
return true
return false
else:
@ -336,10 +337,10 @@ method onContactDetailsUpdated*(self: Module, contactId: string) =
item.senderIcon = updatedContact.icon
item.senderTrustStatus = updatedContact.details.trustStatus
if(item.messageContainsMentions):
let (m, _, err) = self.controller.getMessageDetails(item.id)
let (message, _, err) = self.controller.getMessageDetails(item.id)
if(err.len == 0):
item.messageText = self.controller.getRenderedText(m.parsedText)
item.messageContainsMentions = m.containsContactMentions()
item.messageText = self.controller.getRenderedText(message.parsedText)
item.messageContainsMentions = message.containsContactMentions()
if(self.controller.getMyChatId() == contactId):
self.view.updateChatDetailsNameAndIcon(updatedContact.defaultDisplayName, updatedContact.icon)

View File

@ -20,6 +20,7 @@ type
seen: bool
outgoingStatus: string
messageText: string
unparsedText: string
messageImage: string
messageContainsMentions: bool
sticker: string
@ -64,6 +65,7 @@ proc initItem*(
senderIsAdded: bool,
outgoingStatus,
text,
unparsedText,
image: string,
messageContainsMentions,
seen: bool,
@ -100,7 +102,8 @@ proc initItem*(
result.senderIcon = senderIcon
result.seen = seen
result.outgoingStatus = outgoingStatus
result.messageText = if ContentType.Image == contentType: "" else: text
result.messageText = if contentType == ContentType.Image : "" else: text
result.unparsedText = if contentType == ContentType.Image : "" else: unparsedText
result.messageImage = image
result.messageContainsMentions = messageContainsMentions
result.timestamp = timestamp
@ -131,9 +134,10 @@ proc initItem*(
result.quotedMessageDeleted = quotedMessageDeleted
result.quotedMessageFromIterator = 0
if ContentType.DiscordMessage == contentType:
if contentType == ContentType.DiscordMessage :
if result.messageText == "":
result.messageText = discordMessage.content
result.unparsedText = discordMessage.content
result.senderId = discordMessage.author.id
result.senderDisplayName = discordMessage.author.name
result.senderIcon = discordMessage.author.localUrl
@ -162,6 +166,7 @@ proc initNewMessagesMarkerItem*(clock, timestamp: int64): Item =
senderIsAdded = false,
outgoingStatus = "",
text = "",
unparsedText = "",
image = "",
messageContainsMentions = false,
seen = true,
@ -201,6 +206,7 @@ proc `$`*(self: Item): string =
outgoingStatus:{$self.outgoingStatus},
resendError:{$self.resendError},
messageText:{self.messageText},
unparsedText:{self.unparsedText},
messageContainsMentions:{self.messageContainsMentions},
timestamp:{$self.timestamp},
contentType:{$self.contentType.int},
@ -287,6 +293,12 @@ proc messageText*(self: Item): string {.inline.} =
proc `messageText=`*(self: Item, value: string) {.inline.} =
self.messageText = value
proc unparsedText*(self: Item): string {.inline.} =
self.unparsedText
proc `unparsedText=`*(self: Item, value: string) {.inline.} =
self.unparsedText = value
proc messageImage*(self: Item): string {.inline.} =
self.messageImage
@ -383,6 +395,7 @@ proc toJsonNode*(self: Item): JsonNode =
"seen": self.seen,
"outgoingStatus": self.outgoingStatus,
"messageText": self.messageText,
"unparsedText": self.unparsedText,
"messageImage": self.messageImage,
"messageContainsMentions": self.messageContainsMentions,
"sticker": self.sticker,

View File

@ -97,6 +97,10 @@ QtObject:
QtProperty[string] messageText:
read = messageText
proc unparsedText*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.unparsedText
QtProperty[string] unparsedText:
read = unparsedText
proc messageImage*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.messageImage
QtProperty[string] messageImage:
read = messageImage

View File

@ -21,6 +21,7 @@ type
Seen
OutgoingStatus
MessageText
UnparsedText
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.
@ -114,6 +115,7 @@ QtObject:
ModelRole.ResendError.int:"resendError",
ModelRole.Mentioned.int:"mentioned",
ModelRole.MessageText.int:"messageText",
ModelRole.UnparsedText.int:"unparsedText",
ModelRole.MessageImage.int:"messageImage",
ModelRole.MessageContainsMentions.int:"messageContainsMentions",
ModelRole.Timestamp.int:"timestamp",
@ -205,6 +207,8 @@ QtObject:
result = newQVariant(item.quotedMessageDeleted)
of ModelRole.MessageText:
result = newQVariant(item.messageText)
of ModelRole.UnparsedText:
result = newQVariant(item.unparsedText)
of ModelRole.MessageImage:
result = newQVariant(item.messageImage)
of ModelRole.MessageContainsMentions:
@ -441,7 +445,7 @@ QtObject:
if(self.items[i].pinnedBy == contactId):
roles.add(ModelRole.PinnedBy.int)
if(self.items[i].messageContainsMentions):
roles.add(@[ModelRole.MessageText.int, ModelRole.MessageContainsMentions.int])
roles.add(@[ModelRole.MessageText.int, ModelRole.UnparsedText.int, ModelRole.MessageContainsMentions.int])
if (self.items[i].quotedMessageFrom == contactId):
# If there is a quoted message whom the author changed, increase the iterator to force
@ -496,6 +500,7 @@ QtObject:
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[
ModelRole.MessageText.int,
ModelRole.UnparsedText.int,
ModelRole.MessageContainsMentions.int,
ModelRole.IsEdited.int,
ModelRole.Links.int,

View File

@ -155,6 +155,8 @@ QtObject {
property var stickersModuleInst: stickersModule
property bool isDebugEnabled: advancedModule ? advancedModule.isDebugEnabled : false
property var stickersStore: StickersStore {
stickersModule: stickersModuleInst
}

View File

@ -250,6 +250,7 @@ Item {
senderTrustStatus: model.senderTrustStatus
amISender: model.amISender
messageText: model.messageText
unparsedText: model.unparsedText
messageImage: model.messageImage
messageTimestamp: model.timestamp
messageOutgoingStatus: model.outgoingStatus

View File

@ -31,6 +31,7 @@ StatusMenu {
property int chatType: Constants.chatType.publicChat
property string messageId: ""
property string unparsedText: ""
property string messageSenderId: ""
property int messageContentType: Constants.messageContentType.unknownContentType
property string imageSource: ""
@ -39,7 +40,7 @@ StatusMenu {
property bool isRightClickOnImage: false
property bool pinnedPopup: false
property bool pinMessageAllowedForMembers: false
property bool isDebugEnabled: false
property bool isDebugEnabled: store.isDebugEnabled
property bool isEmoji: false
property bool isSticker: false
property bool hideEmojiPicker: true
@ -127,11 +128,6 @@ StatusMenu {
}
root.emojiReactionsReactedByUser = newEmojiReactions;
/* // copy link feature not ready yet
const numLinkUrls = root.linkUrls.split(" ").length
copyLinkMenu.enabled = numLinkUrls > 1
copyLinkAction.enabled = !!root.linkUrls && numLinkUrls === 1 && !isEmoji && !root.isProfile
*/
popup()
}
@ -361,13 +357,24 @@ StatusMenu {
!root.isRightClickOnImage
}
StatusAction {
id: copyMessageMenuItem
text: qsTr("Copy message")
icon.name: "copy"
onTriggered: {
root.store.copyToClipboard(root.unparsedText)
close()
}
enabled: root.messageContentType === Constants.messageContentType.messageType
}
StatusAction {
id: copyMessageIdAction
text: qsTr("Copy Message Id")
icon.name: "chat"
icon.name: "copy"
enabled: root.isDebugEnabled && !pinnedPopup
onTriggered: {
root.store.copyToClipboard(SelectedMessage.messageId)
root.store.copyToClipboard(root.messageId)
close()
}
}
@ -425,6 +432,7 @@ StatusMenu {
(viewProfileAction.enabled ||
sendMessageMenuItem.enabled ||
replyToMenuItem.enabled ||
copyMessageMenuItem.enabled ||
editMessageAction.enabled ||
pinAction.enabled)
}

View File

@ -48,6 +48,7 @@ Loader {
property bool senderIsAdded: false
property int senderTrustStatus: Constants.trustStatus.unknown
property string messageText: ""
property string unparsedText: ""
property string messageImage: ""
property double messageTimestamp: 0 // We use double, because QML's int is too small
property string messageOutgoingStatus: ""
@ -141,6 +142,7 @@ Loader {
messageContextMenu.chatType = messageStore.getChatType()
messageContextMenu.messageId = root.messageId
messageContextMenu.unparsedText = root.unparsedText
messageContextMenu.messageSenderId = root.senderId
messageContextMenu.messageContentType = root.messageContentType
messageContextMenu.pinnedMessage = root.pinnedMessage