fix(MessageContextMenu): Cleanup. Separate menu for profile. (#10729)
This commit is contained in:
parent
f7e75208a5
commit
5ff4b5a435
|
@ -67,7 +67,9 @@ add_executable(
|
|||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
QML_IMPORT_ROOT="${CMAKE_CURRENT_LIST_DIR}"
|
||||
STATUSQ_MODULE_IMPORT_PATH="${STATUSQ_MODULE_IMPORT_PATH}")
|
||||
STATUSQ_MODULE_IMPORT_PATH="${STATUSQ_MODULE_IMPORT_PATH}"
|
||||
$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_LIB} PUBLIC Qt5::Core Qt5::Gui Qt5::Quick Qt5::QuickControls2)
|
||||
|
|
|
@ -61,6 +61,10 @@ ListModel {
|
|||
title: "TokenListView"
|
||||
section: "Views"
|
||||
}
|
||||
ListElement {
|
||||
title: "MessageContextMenu"
|
||||
section: "Views"
|
||||
}
|
||||
ListElement {
|
||||
title: "StatusCommunityCard"
|
||||
section: "Panels"
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
import AppLayouts.Chat.views.communities 1.0
|
||||
|
||||
import Storybook 1.0
|
||||
import Models 1.0
|
||||
|
||||
import utils 1.0
|
||||
import shared.views.chat 1.0
|
||||
|
||||
SplitView {
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
}
|
||||
|
||||
Logs { id: logs }
|
||||
|
||||
SplitView {
|
||||
orientation: Qt.Vertical
|
||||
SplitView.fillWidth: true
|
||||
|
||||
Rectangle {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
color: Theme.palette.statusAppLayout.rightPanelBackgroundColor
|
||||
clip: true
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
Button {
|
||||
text: "Message context menu"
|
||||
onClicked: {
|
||||
menu1.createObject(this).popup()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Message context menu (hide disabled items)"
|
||||
onClicked: {
|
||||
menu2.createObject(this).popup()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Profile context menu"
|
||||
onClicked: {
|
||||
menu3.createObject(this).popup()
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Profile context menu (hide disabled items)"
|
||||
onClicked: {
|
||||
menu4.createObject(this).popup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: menu1
|
||||
MessageContextMenuView {
|
||||
anchors.centerIn: parent
|
||||
hideDisabledItems: false
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: menu2
|
||||
MessageContextMenuView {
|
||||
anchors.centerIn: parent
|
||||
hideDisabledItems: true
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: menu3
|
||||
ProfileContextMenu {
|
||||
anchors.centerIn: parent
|
||||
hideDisabledItems: false
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: menu4
|
||||
ProfileContextMenu {
|
||||
anchors.centerIn: parent
|
||||
hideDisabledItems: true
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogsAndControlsPanel {
|
||||
id: logsAndControlsPanel
|
||||
|
||||
SplitView.minimumHeight: 100
|
||||
SplitView.preferredHeight: 150
|
||||
|
||||
logsView.logText: logs.logText
|
||||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
SplitView.minimumWidth: 300
|
||||
SplitView.preferredWidth: 300
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 16
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -66,7 +66,6 @@ SplitView {
|
|||
|
||||
sourceComponent: UserListPanel {
|
||||
usersModel: model
|
||||
messageContextMenu: null
|
||||
label: "Some label"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ Menu {
|
|||
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
bottomMargin: 16
|
||||
margins: 16
|
||||
|
||||
onOpened: {
|
||||
if (typeof openHandler === "function") {
|
||||
|
|
|
@ -8,16 +8,16 @@ import StatusQ.Components 0.1
|
|||
import shared 1.0
|
||||
import shared.panels 1.0
|
||||
import shared.status 1.0
|
||||
import shared.views.chat 1.0
|
||||
import utils 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
|
||||
property var store
|
||||
property var usersModel
|
||||
property var messageContextMenu
|
||||
property string label
|
||||
|
||||
StatusBaseText {
|
||||
|
@ -98,15 +98,13 @@ Item {
|
|||
ringSettings.ringSpecModel: model.colorHash
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
// Set parent, X & Y positions for the messageContextMenu
|
||||
messageContextMenu.parent = this
|
||||
messageContextMenu.isProfile = true
|
||||
messageContextMenu.myPublicKey = userProfile.pubKey
|
||||
messageContextMenu.selectedUserPublicKey = model.pubKey
|
||||
messageContextMenu.selectedUserDisplayName = title
|
||||
messageContextMenu.selectedUserIcon = model.icon
|
||||
messageContextMenu.popup(4, 4)
|
||||
} else if (mouse.button === Qt.LeftButton && !!messageContextMenu) {
|
||||
Global.openMenu(profileContextMenuComponent, this, {
|
||||
myPublicKey: userProfile.pubKey,
|
||||
selectedUserPublicKey: model.pubKey,
|
||||
selectedUserDisplayName: title,
|
||||
selectedUserIcon: model.icon,
|
||||
})
|
||||
} else if (mouse.button === Qt.LeftButton) {
|
||||
Global.openProfilePopup(model.pubKey);
|
||||
}
|
||||
}
|
||||
|
@ -136,4 +134,23 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: profileContextMenuComponent
|
||||
|
||||
ProfileContextMenu {
|
||||
store: root.store
|
||||
margins: 8
|
||||
onOpenProfileClicked: {
|
||||
Global.openProfilePopup(publicKey, null)
|
||||
}
|
||||
onCreateOneToOneChat: {
|
||||
Global.changeAppSectionBySectionType(Constants.appSection.chat)
|
||||
root.store.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName)
|
||||
}
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,12 +21,10 @@ SettingsPageLayout {
|
|||
property var pendingMemberRequestsModel
|
||||
property var declinedMemberRequestsModel
|
||||
property string communityName
|
||||
property var communityMemberContextMenu
|
||||
|
||||
property bool editable: true
|
||||
|
||||
signal membershipRequestsClicked()
|
||||
signal userProfileClicked(string id)
|
||||
signal kickUserClicked(string id)
|
||||
signal banUserClicked(string id)
|
||||
signal unbanUserClicked(string id)
|
||||
|
@ -120,7 +118,6 @@ SettingsPageLayout {
|
|||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
onUserProfileClicked: root.userProfileClicked(id)
|
||||
onKickUserClicked: {
|
||||
kickModal.userNameToKick = name
|
||||
kickModal.userIdToKick = id
|
||||
|
@ -151,7 +148,6 @@ SettingsPageLayout {
|
|||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
onUserProfileClicked: root.userProfileClicked(id)
|
||||
onAcceptRequestToJoin: root.acceptRequestToJoin(id)
|
||||
onDeclineRequestToJoin: root.declineRequestToJoin(id)
|
||||
}
|
||||
|
@ -173,7 +169,6 @@ SettingsPageLayout {
|
|||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
onUserProfileClicked: root.userProfileClicked(id)
|
||||
onAcceptRequestToJoin: root.acceptRequestToJoin(id)
|
||||
}
|
||||
|
||||
|
@ -194,7 +189,6 @@ SettingsPageLayout {
|
|||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
onUserProfileClicked: root.userProfileClicked(id)
|
||||
onUnbanUserClicked: root.unbanUserClicked(id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import StatusQ.Components 0.1
|
|||
import StatusQ.Popups 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared.views.chat 1.0
|
||||
import shared.controls.chat 1.0
|
||||
|
||||
import "../../layouts"
|
||||
|
@ -20,7 +21,6 @@ Item {
|
|||
property var model
|
||||
property var communityMemberContextMenu
|
||||
|
||||
signal userProfileClicked(string id)
|
||||
signal kickUserClicked(string id, string name)
|
||||
signal banUserClicked(string id, string name)
|
||||
signal unbanUserClicked(string id)
|
||||
|
@ -146,18 +146,37 @@ Item {
|
|||
|
||||
onClicked: {
|
||||
if(mouse.button === Qt.RightButton) {
|
||||
// Set parent, X & Y positions for the messageContextMenu
|
||||
root.communityMemberContextMenu.parent = this
|
||||
root.communityMemberContextMenu.isProfile = true
|
||||
root.communityMemberContextMenu.selectedUserPublicKey = model.pubKey
|
||||
root.communityMemberContextMenu.selectedUserDisplayName = userName
|
||||
root.communityMemberContextMenu.selectedUserIcon = asset.name
|
||||
root.communityMemberContextMenu.popup()
|
||||
Global.openMenu(memberContextMenuComponent, this, {
|
||||
selectedUserPublicKey: model.pubKey,
|
||||
selectedUserDisplayName: userName,
|
||||
selectedUserIcon: asset.name,
|
||||
})
|
||||
} else {
|
||||
root.userProfileClicked(model.pubKey)
|
||||
Global.openProfilePopup(model.pubKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: memberContextMenuComponent
|
||||
|
||||
ProfileContextMenu {
|
||||
id: memberContextMenuView
|
||||
store: root.rootStore
|
||||
myPublicKey: root.rootStore.myPublicKey()
|
||||
|
||||
onOpenProfileClicked: {
|
||||
Global.openProfilePopup(publicKey, null)
|
||||
}
|
||||
onCreateOneToOneChat: {
|
||||
Global.changeAppSectionBySectionType(Constants.appSection.chat)
|
||||
root.rootStore.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName)
|
||||
}
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import QtGraphicalEffects 1.14
|
|||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
@ -33,6 +34,15 @@ StatusDialog {
|
|||
subtitle: root.messageToPin ? qsTr("Unpin a previous message first")
|
||||
: qsTr("%n message(s)", "", pinnedMessageListView.count)
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
function jumpToMessage(messageId) {
|
||||
root.close()
|
||||
root.messageStore.messageModule.jumpToMessage(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: column
|
||||
|
||||
|
@ -67,7 +77,6 @@ StatusDialog {
|
|||
|
||||
rootStore: root.store
|
||||
messageStore: root.messageStore
|
||||
messageContextMenu: msgContextMenu
|
||||
|
||||
messageId: model.id
|
||||
responseToMessageWithId: model.responseToMessageWithId
|
||||
|
@ -120,13 +129,22 @@ StatusDialog {
|
|||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 55
|
||||
onClicked: {
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
switch (mouse.button) {
|
||||
case Qt.RightButton:
|
||||
Global.openMenu(pinnedPopupMessageContextMenuComponent, this, {
|
||||
messageId: messageItem.messageId,
|
||||
})
|
||||
break
|
||||
case Qt.LeftButton:
|
||||
if (!!root.messageToPin) {
|
||||
if (!radio.checked)
|
||||
radio.checked = true
|
||||
} else {
|
||||
root.close()
|
||||
root.messageStore.messageModule.jumpToMessage(model.id)
|
||||
d.jumpToMessage(model.id)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,24 +178,42 @@ StatusDialog {
|
|||
root.messageToUnpin = checked ? model.id : ""
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pinnedPopupMessageContextMenuComponent
|
||||
|
||||
StatusMenu {
|
||||
id: messageContextMenu
|
||||
|
||||
property string messageId
|
||||
|
||||
StatusAction {
|
||||
text: qsTr("Unpin")
|
||||
icon.name: "unpin"
|
||||
onTriggered: {
|
||||
root.messageStore.unpinMessage(messageContextMenu.messageId)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
MessageContextMenuView {
|
||||
id: msgContextMenu
|
||||
store: root.store
|
||||
pinnedPopup: true
|
||||
pinnedMessage: true
|
||||
onShouldCloseParentPopup: {
|
||||
root.close()
|
||||
StatusAction {
|
||||
text: qsTr("Jump to")
|
||||
icon.name: "arrow-up"
|
||||
onTriggered: {
|
||||
d.jumpToMessage(messageContextMenu.messageId)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
onUnpinMessage: {
|
||||
root.messageStore.unpinMessage(messageId)
|
||||
onOpened: {
|
||||
messageItem.setMessageActive(model.id, true)
|
||||
}
|
||||
onClosed: {
|
||||
messageItem.setMessageActive(model.id, false)
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onJumpToMessage: {
|
||||
root.messageStore.messageModule.jumpToMessage(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -124,6 +124,13 @@ QtObject {
|
|||
messageModule.deleteMessage(messageId)
|
||||
}
|
||||
|
||||
function warnAndDeleteMessage(messageId) {
|
||||
if (localAccountSensitiveSettings.showDeleteMessageWarning)
|
||||
Global.openDeleteMessagePopup(messageId, this)
|
||||
else
|
||||
deleteMessage(messageId)
|
||||
}
|
||||
|
||||
function setEditModeOn(messageId) {
|
||||
if(!messageModule)
|
||||
return
|
||||
|
|
|
@ -209,18 +209,11 @@ QtObject {
|
|||
stickersModuleInst.send(channelId, hash, replyTo, pack, url)
|
||||
}
|
||||
|
||||
// TODO: This seems to be better in Utils.qml
|
||||
function copyToClipboard(text) {
|
||||
globalUtilsInst.copyToClipboard(text)
|
||||
}
|
||||
|
||||
function copyImageToClipboardByUrl(content) {
|
||||
globalUtilsInst.copyImageToClipboardByUrl(content)
|
||||
}
|
||||
|
||||
function downloadImageByUrl(url, path) {
|
||||
globalUtilsInst.downloadImageByUrl(url, path)
|
||||
}
|
||||
|
||||
function isCurrentUser(pubkey) {
|
||||
return userProfileInst.pubKey === pubkey
|
||||
}
|
||||
|
|
|
@ -74,53 +74,10 @@ ColumnLayout {
|
|||
chatSectionModule: root.rootStore.chatCommunitySectionModule
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contextMenuLoader
|
||||
active: root.isActiveChannel
|
||||
asynchronous: true
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
// FIXME: `MessageContextMenuView` is way too heavy
|
||||
// see: https://github.com/status-im/status-desktop/pull/10343#issuecomment-1515103756
|
||||
sourceComponent: MessageContextMenuView {
|
||||
store: root.rootStore
|
||||
reactionModel: root.rootStore.emojiReactionsModel
|
||||
disabledForChat: !root.isUserAllowedToSendMessage
|
||||
|
||||
onPinMessage: {
|
||||
messageStore.pinMessage(messageId)
|
||||
}
|
||||
|
||||
onUnpinMessage: {
|
||||
messageStore.unpinMessage(messageId)
|
||||
}
|
||||
|
||||
onPinnedMessagesLimitReached: {
|
||||
if(!chatContentModule) {
|
||||
console.warn("error on open pinned messages limit reached from message context menu - chat content module is not set")
|
||||
return
|
||||
}
|
||||
Global.openPinnedMessagesPopupRequested(rootStore, messageStore, chatContentModule.pinnedMessagesModel, messageId, root.chatId)
|
||||
}
|
||||
|
||||
onToggleReaction: {
|
||||
messageStore.toggleReaction(messageId, emojiId)
|
||||
}
|
||||
|
||||
onOpenProfileClicked: {
|
||||
Global.openProfilePopup(publicKey, null)
|
||||
}
|
||||
|
||||
onDeleteMessage: {
|
||||
messageStore.deleteMessage(messageId)
|
||||
}
|
||||
|
||||
onEditClicked: messageStore.setEditModeOn(messageId)
|
||||
|
||||
onCreateOneToOneChat: {
|
||||
Global.changeAppSectionBySectionType(Constants.appSection.chat)
|
||||
root.rootStore.chatCommunitySectionModule.createOneToOneChat("", chatId, ensName)
|
||||
}
|
||||
onShowReplyArea: {
|
||||
function showReplyArea(messageId) {
|
||||
let obj = messageStore.getMessageByIdAsJson(messageId)
|
||||
if (!obj) {
|
||||
return
|
||||
|
@ -130,7 +87,6 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
@ -146,30 +102,26 @@ ColumnLayout {
|
|||
chatContentModule: root.chatContentModule
|
||||
rootStore: root.rootStore
|
||||
contactsStore: root.contactsStore
|
||||
messageContextMenu: contextMenuLoader.item
|
||||
messageStore: root.messageStore
|
||||
emojiPopup: root.emojiPopup
|
||||
stickersPopup: root.stickersPopup
|
||||
usersStore: root.usersStore
|
||||
stickersLoaded: root.stickersLoaded
|
||||
publicKey: root.chatId
|
||||
chatId: root.chatId
|
||||
isOneToOne: root.chatType === Constants.chatType.oneToOne
|
||||
isChatBlocked: root.isBlocked || !root.isUserAllowedToSendMessage
|
||||
channelEmoji: !chatContentModule ? "" : (chatContentModule.chatDetails.emoji || "")
|
||||
isActiveChannel: root.isActiveChannel
|
||||
onShowReplyArea: {
|
||||
let obj = messageStore.getMessageByIdAsJson(messageId)
|
||||
if (!obj) {
|
||||
return
|
||||
}
|
||||
if (inputAreaLoader.item) {
|
||||
inputAreaLoader.item.chatInput.showReplyArea(messageId, obj.senderDisplayName, obj.messageText, obj.contentType, obj.messageImage, obj.albumMessageImages, obj.albumImagesCount, obj.sticker)
|
||||
}
|
||||
onShowReplyArea: (messageId, senderId) => {
|
||||
d.showReplyArea(messageId)
|
||||
}
|
||||
onOpenStickerPackPopup: {
|
||||
root.openStickerPackPopup(stickerPackId);
|
||||
}
|
||||
onEditModeChanged: if (!editModeOn && inputAreaLoader.item) inputAreaLoader.item.chatInput.forceInputActiveFocus()
|
||||
onEditModeChanged: {
|
||||
if (!editModeOn && inputAreaLoader.item)
|
||||
inputAreaLoader.item.chatInput.forceInputActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +166,6 @@ ColumnLayout {
|
|||
|
||||
textInput.text: inputAreaLoader.preservedText
|
||||
textInput.placeholderText: root.chatInputPlaceholder
|
||||
messageContextMenu: contextMenuLoader.item
|
||||
emojiPopup: root.emojiPopup
|
||||
stickersPopup: root.stickersPopup
|
||||
isContactBlocked: root.isBlocked
|
||||
|
|
|
@ -33,15 +33,13 @@ Item {
|
|||
property var emojiPopup
|
||||
property var stickersPopup
|
||||
|
||||
property string publicKey: ""
|
||||
property string chatId: ""
|
||||
property bool stickersLoaded: false
|
||||
property alias chatLogView: chatLogView
|
||||
property bool isChatBlocked: false
|
||||
property bool isOneToOne: false
|
||||
property bool isActiveChannel: false
|
||||
|
||||
property var messageContextMenu
|
||||
|
||||
signal openStickerPackPopup(string stickerPackId)
|
||||
signal showReplyArea(string messageId, string author)
|
||||
signal editModeChanged(bool editModeOn)
|
||||
|
@ -250,11 +248,12 @@ Item {
|
|||
emojiPopup: root.emojiPopup
|
||||
stickersPopup: root.stickersPopup
|
||||
chatLogView: ListView.view
|
||||
chatContentModule: root.chatContentModule
|
||||
|
||||
isActiveChannel: root.isActiveChannel
|
||||
isChatBlocked: root.isChatBlocked
|
||||
messageContextMenu: root.messageContextMenu
|
||||
|
||||
chatId: root.chatId
|
||||
messageId: model.id
|
||||
communityId: model.communityId
|
||||
responseToMessageWithId: model.responseToMessageWithId
|
||||
|
@ -322,8 +321,6 @@ Item {
|
|||
root.showReplyArea(messageId, author)
|
||||
}
|
||||
|
||||
onImageClicked: Global.openImagePopup(image, messageContextMenu)
|
||||
|
||||
stickersLoaded: root.stickersLoaded
|
||||
|
||||
onVisibleChanged: {
|
||||
|
@ -364,7 +361,7 @@ Item {
|
|||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Send Contact Request")
|
||||
onClicked: {
|
||||
Global.openContactRequestPopup(root.publicKey, null)
|
||||
Global.openContactRequestPopup(root.chatId, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -379,14 +376,14 @@ Item {
|
|||
text: qsTr("Reject Contact Request")
|
||||
type: StatusBaseButton.Type.Danger
|
||||
onClicked: {
|
||||
root.contactsStore.dismissContactRequest(root.publicKey, "")
|
||||
root.contactsStore.dismissContactRequest(root.chatId, "")
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
text: qsTr("Accept Contact Request")
|
||||
onClicked: {
|
||||
root.contactsStore.acceptContactRequest(root.publicKey, "")
|
||||
root.contactsStore.acceptContactRequest(root.chatId, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,8 +112,9 @@ StatusSectionLayout {
|
|||
rightPanel: Component {
|
||||
id: userListComponent
|
||||
UserListPanel {
|
||||
anchors.fill: parent
|
||||
store: root.rootStore
|
||||
label: qsTr("Members")
|
||||
messageContextMenu: quickActionMessageOptionsMenu
|
||||
usersModel: {
|
||||
let chatContentModule = root.rootStore.currentChatContentModule()
|
||||
if (!chatContentModule || !chatContentModule.usersModule) {
|
||||
|
@ -166,17 +167,4 @@ StatusSectionLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
MessageContextMenuView {
|
||||
id: quickActionMessageOptionsMenu
|
||||
store: root.rootStore
|
||||
|
||||
onOpenProfileClicked: {
|
||||
Global.openProfilePopup(publicKey, null)
|
||||
}
|
||||
onCreateOneToOneChat: {
|
||||
Global.changeAppSectionBySectionType(Constants.appSection.chat)
|
||||
root.rootStore.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,9 +221,7 @@ StatusSectionLayout {
|
|||
declinedMemberRequestsModel: root.community.declinedMemberRequests
|
||||
editable: root.community.amISectionAdmin
|
||||
communityName: root.community.name
|
||||
communityMemberContextMenu: memberContextMenuView
|
||||
|
||||
onUserProfileClicked: Global.openProfilePopup(id)
|
||||
onKickUserClicked: root.rootStore.removeUserFromCommunity(id)
|
||||
onBanUserClicked: root.rootStore.banUserFromCommunity(id)
|
||||
onUnbanUserClicked: root.rootStore.unbanUserFromCommunity(id)
|
||||
|
@ -485,22 +483,6 @@ StatusSectionLayout {
|
|||
}
|
||||
}
|
||||
|
||||
MessageContextMenuView {
|
||||
id: memberContextMenuView
|
||||
store: root.rootStore
|
||||
isProfile: true
|
||||
amIChatAdmin: root.rootStore.amIChatAdmin()
|
||||
myPublicKey: root.rootStore.myPublicKey()
|
||||
|
||||
onOpenProfileClicked: {
|
||||
Global.openProfilePopup(publicKey, null)
|
||||
}
|
||||
onCreateOneToOneChat: {
|
||||
Global.changeAppSectionBySectionType(Constants.appSection.chat)
|
||||
root.rootStore.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.chatCommunitySectionModule
|
||||
function onOpenNoPermissionsToJoinPopup(communityName: string, userName: string, communityId: string, requestId: string) {
|
||||
|
|
|
@ -34,10 +34,11 @@ SettingsContentBase {
|
|||
}
|
||||
|
||||
function openContextMenu(publicKey, name, icon) {
|
||||
contactContextMenu.selectedUserPublicKey = publicKey
|
||||
contactContextMenu.selectedUserDisplayName = name
|
||||
contactContextMenu.selectedUserIcon = icon
|
||||
contactContextMenu.popup()
|
||||
Global.openMenu(contactContextMenuComponent, this, {
|
||||
selectedUserPublicKey: publicKey,
|
||||
selectedUserDisplayName: name,
|
||||
selectedUserIcon: icon,
|
||||
})
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -46,18 +47,23 @@ SettingsContentBase {
|
|||
height: (searchBox.height + contactsTabBar.height
|
||||
+ stackLayout.height + (2 * Style.current.bigPadding))
|
||||
|
||||
MessageContextMenuView {
|
||||
Component {
|
||||
id: contactContextMenuComponent
|
||||
|
||||
ProfileContextMenu {
|
||||
id: contactContextMenu
|
||||
store: ({contactsStore: root.contactsStore})
|
||||
isProfile: true
|
||||
|
||||
onOpenProfileClicked: function (pubkey) {
|
||||
Global.openProfilePopup(pubkey, null)
|
||||
}
|
||||
|
||||
onCreateOneToOneChat: function (communityId, chatId, ensName) {
|
||||
root.contactsStore.joinPrivateChat(chatId)
|
||||
}
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SearchBox {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Dialogs 1.0
|
||||
|
||||
import AppLayouts.Chat.popups 1.0
|
||||
import AppLayouts.Profile.popups 1.0
|
||||
|
@ -40,6 +41,8 @@ QtObject {
|
|||
Global.importCommunityPopupRequested.connect(openImportCommunityPopup)
|
||||
Global.removeContactRequested.connect(openRemoveContactConfirmationPopup)
|
||||
Global.openPopupRequested.connect(openPopup)
|
||||
Global.openDeleteMessagePopup.connect(openDeleteMessagePopup)
|
||||
Global.openDownloadImageDialog.connect(openDownloadImageDialog)
|
||||
}
|
||||
|
||||
function openPopup(popupComponent, params = {}, cb = null) {
|
||||
|
@ -77,9 +80,8 @@ QtObject {
|
|||
openPopup(downloadPageComponent, popupProperties)
|
||||
}
|
||||
|
||||
function openImagePopup(image, contextMenu) {
|
||||
function openImagePopup(image) {
|
||||
var popup = imagePopupComponent.createObject(popupParent)
|
||||
popup.contextMenu = contextMenu
|
||||
popup.openPopup(image)
|
||||
}
|
||||
|
||||
|
@ -206,6 +208,21 @@ QtObject {
|
|||
})
|
||||
}
|
||||
|
||||
function openDeleteMessagePopup(messageId, messageStore) {
|
||||
openPopup(deleteMessageConfirmationDialogComponent,
|
||||
{
|
||||
messageId,
|
||||
messageStore
|
||||
})
|
||||
}
|
||||
|
||||
function openDownloadImageDialog(imageSource) {
|
||||
// We don't use `openPopup`, because there's no `FileDialog::closed` signal.
|
||||
// And multiple file dialogs are (almost) ok
|
||||
const popup = downloadImageDialogComponent.createObject(popupParent, { imageSource })
|
||||
popup.open()
|
||||
}
|
||||
|
||||
readonly property list<Component> _components: [
|
||||
Component {
|
||||
id: removeContactConfirmationDialog
|
||||
|
@ -316,17 +333,6 @@ QtObject {
|
|||
id: imagePopupComponent
|
||||
StatusImageModal {
|
||||
id: imagePopup
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
imagePopup.close()
|
||||
} else if(mouse.button === Qt.RightButton) {
|
||||
contextMenu.imageSource = imagePopup.imageSource
|
||||
contextMenu.hideEmojiPicker = true
|
||||
contextMenu.isRightClickOnImage = true
|
||||
contextMenu.parent = imagePopup.contentItem
|
||||
contextMenu.show()
|
||||
}
|
||||
}
|
||||
onClosed: destroy()
|
||||
}
|
||||
},
|
||||
|
@ -461,6 +467,36 @@ QtObject {
|
|||
DiscordImportProgressDialog {
|
||||
store: root.communitiesStore
|
||||
}
|
||||
},
|
||||
|
||||
Component {
|
||||
id: deleteMessageConfirmationDialogComponent
|
||||
DeleteMessageConfirmationPopup {
|
||||
onClosed: destroy()
|
||||
}
|
||||
},
|
||||
|
||||
Component {
|
||||
id: downloadImageDialogComponent
|
||||
FileDialog {
|
||||
property string imageSource
|
||||
title: qsTr("Please choose a directory")
|
||||
selectFolder: true
|
||||
selectExisting: true
|
||||
selectMultiple: false
|
||||
modality: Qt.NonModal
|
||||
onAccepted: {
|
||||
Utils.downloadImageByUrl(imageSource, fileUrl)
|
||||
destroy()
|
||||
}
|
||||
onRejected: {
|
||||
destroy()
|
||||
}
|
||||
Component.onCompleted: {
|
||||
open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
import utils 1.0
|
||||
import shared 1.0
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property var reactionsModel
|
||||
property var messageReactionsModel: [] // TODO: https://github.com/status-im/status-desktop/issues/10703
|
||||
|
||||
signal toggleReaction(int emojiId)
|
||||
|
||||
spacing: Style.current.halfPadding
|
||||
leftPadding: Style.current.halfPadding
|
||||
rightPadding: Style.current.halfPadding
|
||||
|
||||
Repeater {
|
||||
model: root.reactionsModel
|
||||
delegate: EmojiReaction {
|
||||
source: Style.svg(filename)
|
||||
emojiId: model.emojiId
|
||||
// reactedByUser: !!root.messageReactionsModel[emojiId]
|
||||
onCloseModal: {
|
||||
root.toggleReaction(emojiId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -138,7 +138,7 @@ Item {
|
|||
|
||||
onClicked: {
|
||||
if (!!root.store.profileLargeImage)
|
||||
imageEditMenu.popup(this, mouse.x, mouse.y);
|
||||
Global.openMenu(editImageMenuComponent, this)
|
||||
else
|
||||
Global.openChangeProfilePicPopup(tempIcon);
|
||||
}
|
||||
|
@ -271,8 +271,10 @@ Item {
|
|||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: editImageMenuComponent
|
||||
|
||||
StatusMenu {
|
||||
id: imageEditMenu
|
||||
width: 200
|
||||
|
||||
StatusAction {
|
||||
|
@ -298,3 +300,4 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,3 +14,4 @@ MessageBorder 1.0 MessageBorder.qml
|
|||
EmojiReaction 1.0 EmojiReaction.qml
|
||||
ProfileHeader 1.0 ProfileHeader.qml
|
||||
VerificationLabel 1.0 VerificationLabel.qml
|
||||
MessageReactionsRow 1.0 MessageReactionsRow.qml
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
ConfirmationDialog {
|
||||
id: root
|
||||
|
||||
property var messageStore
|
||||
property string messageId
|
||||
|
||||
header.title: qsTr("Confirm deleting this message")
|
||||
confirmationText: qsTr("Are you sure you want to delete this message? Be aware that other clients are not guaranteed to delete the message as well.")
|
||||
height: 260
|
||||
checkbox.visible: true
|
||||
confirmButtonObjectName: "chatButtonsPanelConfirmDeleteMessageButton"
|
||||
|
||||
executeConfirm: () => {
|
||||
if (checkbox.checked) {
|
||||
localAccountSensitiveSettings.showDeleteMessageWarning = false
|
||||
}
|
||||
close()
|
||||
messageStore.deleteMessage(messageId)
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
|
@ -27,3 +27,4 @@ AccountsModalHeader 1.0 AccountsModalHeader.qml
|
|||
GetSyncCodeInstructionsPopup 1.0 GetSyncCodeInstructionsPopup.qml
|
||||
NoPermissionsToJoinPopup 1.0 NoPermissionsToJoinPopup.qml
|
||||
RemoveAccountConfirmationPopup 1.0 RemoveAccountConfirmationPopup.qml
|
||||
DeleteMessageConfirmationPopup 1.0 DeleteMessageConfirmationPopup.qml
|
||||
|
|
|
@ -63,8 +63,6 @@ Rectangle {
|
|||
|
||||
property var imageErrorMessageLocation: StatusChatInput.ImageErrorMessageLocation.Top // TODO: Remove this proeprty?
|
||||
|
||||
property var messageContextMenu
|
||||
|
||||
property alias suggestions: suggestionsBox
|
||||
|
||||
enum ImageErrorMessageLocation {
|
||||
|
@ -1226,7 +1224,9 @@ Rectangle {
|
|||
Layout.leftMargin: Style.current.halfPadding
|
||||
Layout.rightMargin: Style.current.halfPadding
|
||||
visible: isImage
|
||||
onImageClicked: Global.openImagePopup(chatImage, messageContextMenu)
|
||||
onImageClicked: {
|
||||
Global.openImagePopup(chatImage)
|
||||
}
|
||||
onImageRemoved: {
|
||||
if (control.fileUrlsAndSources.length > index && control.fileUrlsAndSources[index]) {
|
||||
control.fileUrlsAndSources.splice(index, 1)
|
||||
|
|
|
@ -6,13 +6,12 @@ import QtGraphicalEffects 1.13
|
|||
|
||||
import utils 1.0
|
||||
import shared 1.0
|
||||
import shared.views.chat 1.0
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
signal clicked(var mouse)
|
||||
property string imageSource: messageImage.source
|
||||
property var contextMenu
|
||||
property var store
|
||||
|
||||
modal: true
|
||||
Overlay.modal: Rectangle {
|
||||
|
@ -32,7 +31,6 @@ Popup {
|
|||
const maxHeight = Global.applicationWindow.height - 80
|
||||
const maxWidth = Global.applicationWindow.width - 80
|
||||
|
||||
|
||||
if (image.sourceSize.width >= maxWidth || image.sourceSize.height >= maxHeight) {
|
||||
this.width = maxWidth
|
||||
this.height = maxHeight
|
||||
|
@ -44,7 +42,7 @@ Popup {
|
|||
|
||||
function openPopup(image) {
|
||||
setPopupData(image);
|
||||
root.open();
|
||||
open()
|
||||
}
|
||||
|
||||
contentItem: AnimatedImage {
|
||||
|
@ -61,7 +59,22 @@ Popup {
|
|||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
root.clicked(mouse)
|
||||
if (mouse.button === Qt.LeftButton)
|
||||
root.close()
|
||||
if (mouse.button === Qt.RightButton)
|
||||
Global.openMenu(imageContextMenu,
|
||||
messageImage,
|
||||
{ imageSource: messageImage.source })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageContextMenu
|
||||
|
||||
ImageContextMenu {
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import StatusQ.Popups 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
StatusMenu {
|
||||
id: root
|
||||
|
||||
property string imageSource
|
||||
|
||||
StatusAction {
|
||||
text: root.imageSource.endsWith(".gif") ? qsTr("Copy GIF")
|
||||
: qsTr("Copy image")
|
||||
icon.name: "copy"
|
||||
enabled: !!root.imageSource
|
||||
onTriggered: {
|
||||
Utils.copyImageToClipboardByUrl(root.imageSource)
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
text: root.imageSource.endsWith(".gif") ? qsTr("Download GIF")
|
||||
: qsTr("Download image")
|
||||
icon.name: "download"
|
||||
enabled: !!root.imageSource
|
||||
onTriggered: {
|
||||
Global.openDownloadImageDialog(root.imageSource)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ Column {
|
|||
readonly property alias unfurledImagesCount: d.unfurledImagesCount
|
||||
property bool isCurrentUser: false
|
||||
|
||||
signal imageClicked(var image)
|
||||
signal imageClicked(var image, var mouse, var imageSource)
|
||||
signal linksLoaded()
|
||||
|
||||
spacing: 4
|
||||
|
@ -138,7 +138,12 @@ Column {
|
|||
isOnline: root.store.mainModuleInst.isOnline
|
||||
asynchronous: true
|
||||
isAnimated: result.contentType ? result.contentType.toLowerCase().endsWith("gif") : false
|
||||
onClicked: isAnimated && !playing ? localAnimationEnabled = true : root.imageClicked(linkImage.imageAlias)
|
||||
onClicked: {
|
||||
if (isAnimated && !playing)
|
||||
localAnimationEnabled = true
|
||||
else
|
||||
root.imageClicked(linkImage.imageAlias, mouse, source)
|
||||
}
|
||||
imageAlias.cache: localAnimationEnabled // GIFs can only loop/play properly with cache enabled
|
||||
Loader {
|
||||
width: 45
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import StatusQ.Popups 0.1
|
||||
|
||||
import shared.controls.chat 1.0
|
||||
|
||||
StatusMenu {
|
||||
id: root
|
||||
|
||||
property alias reactionsModel: emojiRow.reactionsModel
|
||||
property alias messageReactionsModel: emojiRow.messageReactionsModel
|
||||
|
||||
signal toggleReaction(int emojiId)
|
||||
|
||||
width: emojiRow.width
|
||||
|
||||
MessageReactionsRow {
|
||||
id: emojiRow
|
||||
onToggleReaction: {
|
||||
root.toggleReaction(emojiId)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ import QtQuick 2.12
|
|||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQml.Models 2.3
|
||||
import QtQuick.Dialogs 1.0
|
||||
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
@ -20,330 +19,56 @@ StatusMenu {
|
|||
|
||||
property var store
|
||||
property var reactionModel
|
||||
property alias emojiContainer: emojiContainer
|
||||
|
||||
property string myPublicKey: ""
|
||||
property bool amIChatAdmin: false
|
||||
property bool disabledForChat: false
|
||||
|
||||
property string selectedUserPublicKey: ""
|
||||
property string selectedUserDisplayName: ""
|
||||
property string selectedUserIcon: ""
|
||||
|
||||
property int chatType: Constants.chatType.unknown
|
||||
property string messageId: ""
|
||||
property string unparsedText: ""
|
||||
property string messageSenderId: ""
|
||||
property int messageContentType: Constants.messageContentType.unknownContentType
|
||||
property string imageSource: ""
|
||||
|
||||
property bool isProfile: false
|
||||
property bool isRightClickOnImage: false
|
||||
property bool pinnedPopup: false
|
||||
property bool pinMessageAllowedForMembers: false
|
||||
property bool isDebugEnabled: store && store.isDebugEnabled
|
||||
property bool isEmoji: false
|
||||
property bool isSticker: false
|
||||
property bool hideEmojiPicker: true
|
||||
property bool editRestricted: false
|
||||
property bool pinnedMessage: false
|
||||
property bool canPin: false
|
||||
|
||||
readonly property bool isMyMessage: {
|
||||
return root.messageSenderId !== "" && root.messageSenderId === root.myPublicKey;
|
||||
}
|
||||
readonly property bool isMe: {
|
||||
return root.selectedUserPublicKey === root.store.contactsStore.myPublicKey;
|
||||
}
|
||||
readonly property var contactDetails: {
|
||||
if (root.selectedUserPublicKey === "" || isMe) {
|
||||
return {}
|
||||
}
|
||||
return Utils.getContactDetailsAsJson(root.selectedUserPublicKey);
|
||||
}
|
||||
readonly property bool isContact: {
|
||||
return root.selectedUserPublicKey !== "" && !!contactDetails.isContact
|
||||
}
|
||||
readonly property bool isBlockedContact: (!!contactDetails && contactDetails.isBlocked) || false
|
||||
|
||||
readonly property int outgoingVerificationStatus: {
|
||||
if (root.selectedUserPublicKey === "" || root.isMe || !root.isContact) {
|
||||
return 0
|
||||
}
|
||||
return contactDetails.verificationStatus
|
||||
}
|
||||
readonly property int incomingVerificationStatus: {
|
||||
if (root.selectedUserPublicKey === "" || root.isMe || !root.isContact) {
|
||||
return 0
|
||||
}
|
||||
return contactDetails.incomingVerificationStatus
|
||||
}
|
||||
readonly property bool hasPendingContactRequest: {
|
||||
return !root.isMe && root.selectedUserPublicKey !== "" &&
|
||||
root.store.contactsStore.hasPendingContactRequest(root.selectedUserPublicKey);
|
||||
}
|
||||
readonly property bool hasActiveReceivedVerificationRequestFrom: {
|
||||
if (!root.selectedUserPublicKey || root.isMe || !root.isContact) {
|
||||
return false
|
||||
}
|
||||
return contactDetails.incomingVerificationStatus === Constants.verificationStatus.verifying ||
|
||||
contactDetails.incomingVerificationStatus === Constants.verificationStatus.verified
|
||||
}
|
||||
readonly property bool isVerificationRequestSent: {
|
||||
if (!root.selectedUserPublicKey || root.isMe || !root.isContact) {
|
||||
return false
|
||||
}
|
||||
return root.outgoingVerificationStatus !== Constants.verificationStatus.unverified &&
|
||||
root.outgoingVerificationStatus !== Constants.verificationStatus.verified &&
|
||||
root.outgoingVerificationStatus !== Constants.verificationStatus.trusted
|
||||
}
|
||||
readonly property bool isTrusted: {
|
||||
if (!root.selectedUserPublicKey || root.isMe || !root.isContact) {
|
||||
return false
|
||||
}
|
||||
return root.outgoingVerificationStatus === Constants.verificationStatus.trusted ||
|
||||
root.incomingVerificationStatus === Constants.verificationStatus.trusted
|
||||
}
|
||||
|
||||
readonly property bool userTrustIsUnknown: contactDetails && contactDetails.trustStatus === Constants.trustStatus.unknown
|
||||
readonly property bool userIsUntrustworthy: contactDetails && contactDetails.trustStatus === Constants.trustStatus.untrustworthy
|
||||
|
||||
property var emojiReactionsReactedByUser: []
|
||||
|
||||
signal openProfileClicked(string publicKey)
|
||||
signal pinMessage(string messageId)
|
||||
signal unpinMessage(string messageId)
|
||||
signal pinnedMessagesLimitReached(string messageId)
|
||||
signal jumpToMessage(string messageId)
|
||||
signal shouldCloseParentPopup()
|
||||
signal createOneToOneChat(string communityId, string chatId, string ensName)
|
||||
signal showReplyArea()
|
||||
signal showReplyArea(string messageId, string messageSenderId)
|
||||
signal toggleReaction(string messageId, int emojiId)
|
||||
signal deleteMessage(string messageId)
|
||||
signal editClicked(string messageId)
|
||||
|
||||
function show(userNameParam, fromAuthorParam, identiconParam, textParam, nicknameParam, emojiReactionsModel) {
|
||||
let newEmojiReactions = []
|
||||
if (!!emojiReactionsModel) {
|
||||
emojiReactionsModel.forEach(function (emojiReaction) {
|
||||
newEmojiReactions[emojiReaction.emojiId] = emojiReaction.currentUserReacted
|
||||
})
|
||||
}
|
||||
root.emojiReactionsReactedByUser = newEmojiReactions;
|
||||
|
||||
popup()
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
// Reset selectedUserPublicKey so that associated properties get recalculated on re-open
|
||||
selectedUserPublicKey = ""
|
||||
}
|
||||
|
||||
width: Math.max(emojiContainer.visible ? emojiContainer.width : 0,
|
||||
(root.isRightClickOnImage && !root.pinnedPopup) ? 176 : 230)
|
||||
width: Math.max(emojiContainer.visible ? emojiContainer.width : 0, 230)
|
||||
|
||||
Item {
|
||||
id: emojiContainer
|
||||
width: emojiRow.width
|
||||
height: visible ? emojiRow.height : 0
|
||||
visible: !root.hideEmojiPicker && (root.isEmoji || !root.isProfile) && !root.pinnedPopup && !root.disabledForChat
|
||||
visible: !root.disabledForChat
|
||||
|
||||
Row {
|
||||
MessageReactionsRow {
|
||||
id: emojiRow
|
||||
spacing: Style.current.halfPadding
|
||||
leftPadding: Style.current.halfPadding
|
||||
rightPadding: Style.current.halfPadding
|
||||
bottomPadding: root.isEmoji ? 0 : Style.current.padding
|
||||
|
||||
Repeater {
|
||||
model: root.reactionModel
|
||||
delegate: EmojiReaction {
|
||||
source: Style.svg(filename)
|
||||
emojiId: model.emojiId
|
||||
reactedByUser: !!root.emojiReactionsReactedByUser[model.emojiId]
|
||||
onCloseModal: {
|
||||
reactionsModel: root.reactionModel
|
||||
bottomPadding: Style.current.padding
|
||||
onToggleReaction: {
|
||||
root.toggleReaction(root.messageId, emojiId)
|
||||
root.close()
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProfileHeader {
|
||||
visible: root.isProfile
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
|
||||
displayNameVisible: false
|
||||
displayNamePlusIconsVisible: true
|
||||
editButtonVisible: false
|
||||
displayName: root.selectedUserDisplayName
|
||||
pubkey: root.selectedUserPublicKey
|
||||
icon: root.selectedUserIcon
|
||||
trustStatus: contactDetails && contactDetails.trustStatus ? contactDetails.trustStatus
|
||||
: Constants.trustStatus.unknown
|
||||
isContact: root.isContact
|
||||
isCurrentUser: root.isMe
|
||||
userIsEnsVerified: (!!contactDetails && contactDetails.ensVerified) || false
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: root.isProfile
|
||||
height: visible ? root.topPadding : 0
|
||||
}
|
||||
|
||||
StatusMenuSeparator {
|
||||
anchors.bottom: viewProfileAction.top
|
||||
visible: !root.isEmoji && !root.hideEmojiPicker && !pinnedPopup
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: copyImageAction
|
||||
text: (root.imageSource.endsWith(".gif")) ? qsTr("Copy GIF") : qsTr("Copy image")
|
||||
onTriggered: {
|
||||
if (root.imageSource) {
|
||||
root.store.copyImageToClipboardByUrl(root.imageSource)
|
||||
}
|
||||
root.close()
|
||||
}
|
||||
icon.name: "copy"
|
||||
enabled: root.isRightClickOnImage && !root.pinnedPopup
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: downloadImageAction
|
||||
text: (root.imageSource.endsWith(".gif")) ? qsTr("Download GIF") : qsTr("Download image")
|
||||
onTriggered: {
|
||||
fileDialog.open()
|
||||
root.close()
|
||||
}
|
||||
icon.name: "download"
|
||||
enabled: root.isRightClickOnImage && !root.pinnedPopup
|
||||
}
|
||||
|
||||
ViewProfileMenuItem {
|
||||
id: viewProfileAction
|
||||
enabled: root.isProfile && !root.pinnedPopup
|
||||
onTriggered: {
|
||||
root.openProfileClicked(root.selectedUserPublicKey)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
SendMessageMenuItem {
|
||||
id: sendMessageMenuItem
|
||||
enabled: root.isProfile && root.isContact && !root.isBlockedContact
|
||||
onTriggered: {
|
||||
root.createOneToOneChat("", root.selectedUserPublicKey, "")
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
SendContactRequestMenuItem {
|
||||
id: sendContactRequestMenuItem
|
||||
enabled: root.isProfile && !root.isMe && !root.isContact
|
||||
&& !root.isBlockedContact && !root.hasPendingContactRequest
|
||||
onTriggered: {
|
||||
Global.openContactRequestPopup(root.selectedUserPublicKey, null)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: verifyIdentityAction
|
||||
text: qsTr("Verify Identity")
|
||||
icon.name: "checkmark-circle"
|
||||
enabled: root.isProfile && !root.isMe && root.isContact
|
||||
&& !root.isBlockedContact
|
||||
&& root.outgoingVerificationStatus === Constants.verificationStatus.unverified
|
||||
&& !root.hasActiveReceivedVerificationRequestFrom
|
||||
onTriggered: {
|
||||
Global.openSendIDRequestPopup(root.selectedUserPublicKey, null)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: pendingIdentityAction
|
||||
text: isVerificationRequestSent ||
|
||||
root.incomingVerificationStatus === Constants.verificationStatus.verified ?
|
||||
qsTr("ID Request Pending....") :
|
||||
qsTr("Respond to ID Request...")
|
||||
icon.name: "checkmark-circle"
|
||||
enabled: root.isProfile && !root.isMe && root.isContact
|
||||
&& !root.isBlockedContact && !root.isTrusted
|
||||
&& (root.hasActiveReceivedVerificationRequestFrom
|
||||
|| root.isVerificationRequestSent)
|
||||
onTriggered: {
|
||||
if (hasActiveReceivedVerificationRequestFrom) {
|
||||
Global.openIncomingIDRequestPopup(root.selectedUserPublicKey, null)
|
||||
} else if (root.isVerificationRequestSent) {
|
||||
Global.openOutgoingIDRequestPopup(root.selectedUserPublicKey, null)
|
||||
}
|
||||
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: renameAction
|
||||
text: qsTr("Rename")
|
||||
icon.name: "edit_pencil"
|
||||
enabled: root.isProfile && !root.isMe
|
||||
onTriggered: {
|
||||
Global.openNicknamePopupRequested(root.selectedUserPublicKey, contactDetails.localNickname,
|
||||
"%1 (%2)".arg(root.selectedUserDisplayName).arg(Utils.getElidedCompressedPk(root.selectedUserPublicKey)))
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: unblockAction
|
||||
text: qsTr("Unblock User")
|
||||
icon.name: "remove-circle"
|
||||
enabled: root.isProfile && !root.isMe && root.isBlockedContact
|
||||
onTriggered: Global.unblockContactRequested(root.selectedUserPublicKey, root.selectedUserDisplayName)
|
||||
}
|
||||
|
||||
StatusMenuSeparator {
|
||||
visible: blockMenuItem.enabled || markUntrustworthyMenuItem.enabled || removeUntrustworthyMarkMenuItem.enabled
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: markUntrustworthyMenuItem
|
||||
text: qsTr("Mark as Untrustworthy")
|
||||
icon.name: "warning"
|
||||
type: StatusAction.Type.Danger
|
||||
enabled: root.isProfile && !root.isMe && root.userTrustIsUnknown
|
||||
onTriggered: root.store.contactsStore.markUntrustworthy(root.selectedUserPublicKey)
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: removeUntrustworthyMarkMenuItem
|
||||
text: qsTr("Remove Untrustworthy Mark")
|
||||
icon.name: "warning"
|
||||
enabled: root.isProfile && !root.isMe && root.userIsUntrustworthy
|
||||
onTriggered: root.store.contactsStore.removeTrustStatus(root.selectedUserPublicKey)
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
text: qsTr("Remove Contact")
|
||||
icon.name: "remove-contact"
|
||||
type: StatusAction.Type.Danger
|
||||
enabled: root.isContact && !root.isBlockedContact && !root.hasPendingContactRequest
|
||||
onTriggered: {
|
||||
Global.removeContactRequested(root.selectedUserDisplayName, root.selectedUserPublicKey);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: blockMenuItem
|
||||
text: qsTr("Block User")
|
||||
icon.name: "cancel"
|
||||
type: StatusAction.Type.Danger
|
||||
enabled: root.isProfile && !root.isMe && !root.isBlockedContact
|
||||
onTriggered: Global.blockContactRequested(root.selectedUserPublicKey, root.selectedUserDisplayName)
|
||||
visible: emojiContainer.visible
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
|
@ -351,15 +76,10 @@ StatusMenu {
|
|||
text: qsTr("Reply to")
|
||||
icon.name: "chat"
|
||||
onTriggered: {
|
||||
root.showReplyArea()
|
||||
root.showReplyArea(root.messageId, root.messageSenderId)
|
||||
root.close()
|
||||
}
|
||||
enabled: (!root.hideEmojiPicker &&
|
||||
!root.isEmoji &&
|
||||
!root.isProfile &&
|
||||
!root.pinnedPopup &&
|
||||
!root.isRightClickOnImage &&
|
||||
!root.disabledForChat)
|
||||
enabled: !root.disabledForChat
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
|
@ -370,12 +90,7 @@ StatusMenu {
|
|||
}
|
||||
icon.name: "edit"
|
||||
enabled: root.isMyMessage &&
|
||||
!root.hideEmojiPicker &&
|
||||
!root.isEmoji &&
|
||||
!root.isSticker &&
|
||||
!root.isProfile &&
|
||||
!root.pinnedPopup &&
|
||||
!root.isRightClickOnImage &&
|
||||
!root.editRestricted &&
|
||||
!root.disabledForChat
|
||||
}
|
||||
|
||||
|
@ -403,13 +118,8 @@ StatusMenu {
|
|||
|
||||
StatusAction {
|
||||
id: pinAction
|
||||
text: {
|
||||
if (root.pinnedMessage) {
|
||||
return qsTr("Unpin")
|
||||
}
|
||||
return qsTr("Pin")
|
||||
|
||||
}
|
||||
text: root.pinnedMessage ? qsTr("Unpin") : qsTr("Pin")
|
||||
icon.name: root.pinnedMessage ? "unpin" : "pin"
|
||||
onTriggered: {
|
||||
if (root.pinnedMessage) {
|
||||
root.unpinMessage(root.messageId)
|
||||
|
@ -424,14 +134,10 @@ StatusMenu {
|
|||
root.pinMessage(root.messageId)
|
||||
root.close()
|
||||
}
|
||||
icon.name: "pin"
|
||||
enabled: {
|
||||
if (root.isProfile || root.isEmoji || root.isRightClickOnImage || root.disabledForChat)
|
||||
if (root.disabledForChat)
|
||||
return false
|
||||
|
||||
if (root.pinnedPopup)
|
||||
return true
|
||||
|
||||
switch (root.chatType) {
|
||||
case Constants.chatType.profile:
|
||||
return false
|
||||
|
@ -449,10 +155,9 @@ StatusMenu {
|
|||
|
||||
StatusMenuSeparator {
|
||||
visible: deleteMessageAction.enabled &&
|
||||
(viewProfileAction.enabled ||
|
||||
sendMessageMenuItem.enabled ||
|
||||
replyToMenuItem.enabled ||
|
||||
(replyToMenuItem.enabled ||
|
||||
copyMessageMenuItem.enabled ||
|
||||
copyMessageIdAction ||
|
||||
editMessageAction.enabled ||
|
||||
pinAction.enabled)
|
||||
}
|
||||
|
@ -461,72 +166,16 @@ StatusMenu {
|
|||
id: deleteMessageAction
|
||||
enabled: (root.isMyMessage || root.amIChatAdmin) &&
|
||||
!root.disabledForChat &&
|
||||
!root.isProfile &&
|
||||
!root.isEmoji &&
|
||||
!root.pinnedPopup &&
|
||||
!root.isRightClickOnImage &&
|
||||
(root.messageContentType === Constants.messageContentType.messageType ||
|
||||
root.messageContentType === Constants.messageContentType.stickerType ||
|
||||
root.messageContentType === Constants.messageContentType.emojiType ||
|
||||
root.messageContentType === Constants.messageContentType.imageType ||
|
||||
root.messageContentType === Constants.messageContentType.audioType)
|
||||
text: qsTr("Delete message")
|
||||
onTriggered: {
|
||||
if (!localAccountSensitiveSettings.showDeleteMessageWarning) {
|
||||
deleteMessage(messageId)
|
||||
}
|
||||
else {
|
||||
Global.openPopup(deleteMessageConfirmationDialogComponent)
|
||||
}
|
||||
}
|
||||
icon.name: "delete"
|
||||
type: StatusAction.Type.Danger
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: jumpToAction
|
||||
enabled: root.pinnedPopup && !root.isProfile
|
||||
text: qsTr("Jump to")
|
||||
onTriggered: {
|
||||
root.jumpToMessage(root.messageId)
|
||||
root.close()
|
||||
root.shouldCloseParentPopup()
|
||||
}
|
||||
icon.name: "arrow-up"
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
title: qsTr("Please choose a directory")
|
||||
selectFolder: true
|
||||
selectExisting: true
|
||||
selectMultiple: false
|
||||
modality: Qt.NonModal
|
||||
onAccepted: {
|
||||
if (root.imageSource) {
|
||||
root.store.downloadImageByUrl(root.imageSource, fileDialog.fileUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: deleteMessageConfirmationDialogComponent
|
||||
ConfirmationDialog {
|
||||
header.title: qsTr("Confirm deleting this message")
|
||||
confirmationText: qsTr("Are you sure you want to delete this message? Be aware that other clients are not guaranteed to delete the message as well.")
|
||||
height: 260
|
||||
checkbox.visible: true
|
||||
executeConfirm: function () {
|
||||
if (checkbox.checked) {
|
||||
localAccountSensitiveSettings.showDeleteMessageWarning = false
|
||||
}
|
||||
|
||||
close()
|
||||
root.deleteMessage(messageId)
|
||||
}
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ Loader {
|
|||
property var messageStore
|
||||
property var usersStore
|
||||
property var contactsStore
|
||||
property var messageContextMenu: null
|
||||
property var chatContentModule
|
||||
|
||||
property string channelEmoji
|
||||
property bool isActiveChannel: false
|
||||
|
||||
|
@ -34,6 +35,7 @@ Loader {
|
|||
// without an explicit need to fetch those details via message store/module.
|
||||
property bool isChatBlocked: false
|
||||
|
||||
property string chatId
|
||||
property string messageId: ""
|
||||
property string communityId: ""
|
||||
|
||||
|
@ -120,67 +122,59 @@ Loader {
|
|||
readonly property bool isExpired: d.getIsExpired(messageTimestamp, messageOutgoingStatus)
|
||||
readonly property bool isSending: messageOutgoingStatus === Constants.sending && !isExpired
|
||||
|
||||
signal imageClicked(var image)
|
||||
|
||||
// WARNING: To much arguments here. Create an object argument.
|
||||
property var messageClickHandler: function(sender, point,
|
||||
isProfileClick,
|
||||
isSticker = false,
|
||||
isImage = false,
|
||||
image = null,
|
||||
isEmoji = false,
|
||||
hideEmojiPicker = false,
|
||||
isReply = false,
|
||||
isRightClickOnImage = false,
|
||||
imageSource = "") {
|
||||
|
||||
if (placeholderMessage || !(root.rootStore.mainModuleInst.activeSection.joined || isProfileClick)) {
|
||||
function openProfileContextMenu(sender, mouse, isReply = false) {
|
||||
if (isReply && !quotedMessageFrom) {
|
||||
// The responseTo message was deleted
|
||||
// so we don't enable to right click the unavailable profile
|
||||
return false
|
||||
}
|
||||
|
||||
messageContextMenu.myPublicKey = userProfile.pubKey
|
||||
messageContextMenu.amIChatAdmin = root.amIChatAdmin
|
||||
messageContextMenu.pinMessageAllowedForMembers = messageStore.isPinMessageAllowedForMembers
|
||||
messageContextMenu.chatType = messageStore.chatType
|
||||
|
||||
messageContextMenu.messageId = root.messageId
|
||||
messageContextMenu.unparsedText = root.unparsedText
|
||||
messageContextMenu.messageSenderId = root.senderId
|
||||
messageContextMenu.messageContentType = root.messageContentType
|
||||
messageContextMenu.pinnedMessage = root.pinnedMessage
|
||||
messageContextMenu.canPin = !!root.messageStore && root.messageStore.getNumberOfPinnedMessages() < Constants.maxNumberOfPins
|
||||
|
||||
messageContextMenu.selectedUserPublicKey = root.senderId
|
||||
messageContextMenu.selectedUserDisplayName = root.senderDisplayName
|
||||
messageContextMenu.selectedUserIcon = root.senderIcon
|
||||
|
||||
messageContextMenu.imageSource = imageSource
|
||||
|
||||
messageContextMenu.isProfile = !!isProfileClick
|
||||
messageContextMenu.isRightClickOnImage = isRightClickOnImage
|
||||
messageContextMenu.isEmoji = isEmoji
|
||||
messageContextMenu.isSticker = isSticker
|
||||
messageContextMenu.hideEmojiPicker = hideEmojiPicker
|
||||
|
||||
if (isReply) {
|
||||
if (!quotedMessageFrom) {
|
||||
// The responseTo message was deleted so we don't eneble to right click the unaviable profile
|
||||
return false
|
||||
}
|
||||
messageContextMenu.messageSenderId = quotedMessageFrom
|
||||
messageContextMenu.selectedUserPublicKey = quotedMessageFrom
|
||||
messageContextMenu.selectedUserDisplayName = quotedMessageAuthorDetailsDisplayName
|
||||
messageContextMenu.selectedUserIcon = quotedMessageAuthorDetailsThumbnailImage
|
||||
const params = {
|
||||
selectedUserPublicKey: isReply ? quotedMessageFrom : root.senderId,
|
||||
selectedUserDisplayName: isReply ? quotedMessageAuthorDetailsDisplayName : root.senderDisplayName,
|
||||
selectedUserIcon: isReply ? quotedMessageAuthorDetailsThumbnailImage : root.senderIcon,
|
||||
}
|
||||
|
||||
// Emoji container is not a menu item of messageContextMenu so checking it separatly
|
||||
if (messageContextMenu.checkIfEmpty() && !isEmoji) {
|
||||
return false
|
||||
Global.openMenu(profileContextMenuComponent, sender, params)
|
||||
}
|
||||
|
||||
messageContextMenu.parent = sender
|
||||
messageContextMenu.popup(point)
|
||||
return true
|
||||
function openMessageContextMenu() {
|
||||
if (placeholderMessage || !root.rootStore.mainModuleInst.activeSection.joined)
|
||||
return
|
||||
|
||||
const params = {
|
||||
myPublicKey: userProfile.pubKey,
|
||||
amIChatAdmin: root.amIChatAdmin,
|
||||
pinMessageAllowedForMembers: messageStore.isPinMessageAllowedForMembers,
|
||||
chatType: messageStore.chatType,
|
||||
|
||||
messageId: root.messageId,
|
||||
unparsedText: root.unparsedText,
|
||||
messageSenderId: root.senderId,
|
||||
messageContentType: root.messageContentType,
|
||||
pinnedMessage: root.pinnedMessage,
|
||||
canPin: !!root.messageStore && root.messageStore.getNumberOfPinnedMessages() < Constants.maxNumberOfPins,
|
||||
editRestricted: root.isSticker || root.isImage,
|
||||
}
|
||||
|
||||
Global.openMenu(messageContextMenuComponent, this, params)
|
||||
}
|
||||
|
||||
function setMessageActive(messageId, active) {
|
||||
|
||||
// TODO: Is argument messageId actually needed?
|
||||
// It was probably used with dynamic scoping,
|
||||
// but not this method can be moved to private `d`.
|
||||
// Probably that it was done this way, because `MessageView` is reused as delegate.
|
||||
|
||||
if (active) {
|
||||
d.activeMessage = messageId;
|
||||
return;
|
||||
}
|
||||
if (d.activeMessage === messageId) {
|
||||
d.activeMessage = "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
signal showReplyArea(string messageId, string author)
|
||||
|
@ -237,22 +231,7 @@ Loader {
|
|||
property string activeMessage
|
||||
readonly property bool isMessageActive: d.activeMessage === root.messageId
|
||||
|
||||
function setMessageActive(messageId, active) {
|
||||
|
||||
// TODO: Is argument messageId actually needed?
|
||||
// It was probably used with dynamic scoping,
|
||||
// but not this method can be moved to private `d`.
|
||||
// Probably that it was done this way, because `MessageView` is reused as delegate.
|
||||
|
||||
if (active) {
|
||||
d.activeMessage = messageId;
|
||||
return;
|
||||
}
|
||||
if (d.activeMessage === messageId) {
|
||||
d.activeMessage = "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
readonly property bool addReactionAllowed: !root.isInPinnedPopup && !root.isChatBlocked
|
||||
|
||||
function nextMessageHasHeader() {
|
||||
if(!root.nextMessageAsJsonObj) {
|
||||
|
@ -303,15 +282,24 @@ Loader {
|
|||
return StatusMessage.ContentType.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
function addReactionClicked(mouseArea, mouse) {
|
||||
if (!d.addReactionAllowed)
|
||||
return
|
||||
// Don't use mouseArea as parent, as it will be destroyed right after opening menu
|
||||
const point = mouseArea.mapToItem(root, mouse.x, mouse.y)
|
||||
Global.openMenu(addReactionContextMenu, root, {}, point)
|
||||
}
|
||||
|
||||
|
||||
|
||||
Connections {
|
||||
enabled: d.isMessageActive
|
||||
target: root.messageContextMenu
|
||||
function onClosed() {
|
||||
d.setMessageActive(root.messageId, false)
|
||||
function onImageClicked(image, mouse, imageSource) {
|
||||
switch (mouse.button) {
|
||||
case Qt.LeftButton:
|
||||
Global.openImagePopup(image)
|
||||
break;
|
||||
case Qt.RightButton:
|
||||
Global.openMenu(imageContextMenuComponent, image, { imageSource })
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,7 +486,6 @@ Loader {
|
|||
disableHover: root.disableHover ||
|
||||
delegate.hideQuickActions ||
|
||||
(root.chatLogView && root.chatLogView.moving) ||
|
||||
(root.messageContextMenu && root.messageContextMenu.opened) ||
|
||||
Global.popupOpened
|
||||
|
||||
disableEmojis: root.isChatBlocked
|
||||
|
@ -514,15 +501,8 @@ Loader {
|
|||
|
||||
onEditCompleted: delegate.editCompletedHandler(newMsgText)
|
||||
|
||||
onImageClicked: {
|
||||
switch (mouse.button) {
|
||||
case Qt.LeftButton:
|
||||
root.imageClicked(image, mouse);
|
||||
break;
|
||||
case Qt.RightButton:
|
||||
root.messageClickHandler(image, Qt.point(mouse.x, mouse.y), false, false, true, image, false, true, false, true, imageSource)
|
||||
break;
|
||||
}
|
||||
onImageClicked: (image, mouse, imageSource) => {
|
||||
d.onImageClicked(image, mouse, imageSource)
|
||||
}
|
||||
|
||||
onLinkActivated: {
|
||||
|
@ -542,13 +522,11 @@ Loader {
|
|||
}
|
||||
|
||||
onProfilePictureClicked: {
|
||||
if (root.messageClickHandler(sender, Qt.point(mouse.x, mouse.y), true))
|
||||
d.setMessageActive(root.messageId, true)
|
||||
root.openProfileContextMenu(sender, mouse)
|
||||
}
|
||||
|
||||
onReplyProfileClicked: {
|
||||
if (root.messageClickHandler(sender, Qt.point(mouse.x, mouse.y), true, false, false, null, false, false, true))
|
||||
d.setMessageActive(root.messageId, true)
|
||||
root.openProfileContextMenu(sender, mouse, true)
|
||||
}
|
||||
|
||||
onReplyMessageClicked: {
|
||||
|
@ -557,8 +535,7 @@ Loader {
|
|||
}
|
||||
|
||||
onSenderNameClicked: {
|
||||
if (root.messageClickHandler(sender, Qt.point(mouse.x, mouse.y), true))
|
||||
d.setMessageActive(root.messageId, true)
|
||||
root.openProfileContextMenu(sender, mouse)
|
||||
}
|
||||
|
||||
onToggleReactionClicked: {
|
||||
|
@ -573,12 +550,8 @@ Loader {
|
|||
root.messageStore.toggleReaction(root.messageId, emojiId)
|
||||
}
|
||||
|
||||
onAddReactionClicked: {
|
||||
if (root.isChatBlocked)
|
||||
return
|
||||
|
||||
if (root.messageClickHandler(sender, Qt.point(mouse.x, mouse.y), false, false, false, null, true, false))
|
||||
d.setMessageActive(root.messageId, true)
|
||||
onAddReactionClicked: (sender, mouse) => {
|
||||
d.addReactionClicked(sender, mouse)
|
||||
}
|
||||
|
||||
onStickerClicked: {
|
||||
|
@ -592,12 +565,9 @@ Loader {
|
|||
mouseArea {
|
||||
acceptedButtons: Qt.RightButton
|
||||
enabled: !root.isChatBlocked &&
|
||||
!root.placeholderMessage &&
|
||||
delegate.contentType !== StatusMessage.ContentType.Image
|
||||
!root.placeholderMessage
|
||||
onClicked: {
|
||||
if (root.messageClickHandler(this, Qt.point(mouse.x, mouse.y),
|
||||
false, false, false, null, root.isEmoji, false, false, false, ""))
|
||||
d.setMessageActive(root.messageId, true)
|
||||
root.openMessageContextMenu()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -714,7 +684,6 @@ Loader {
|
|||
usersStore: root.usersStore
|
||||
emojiPopup: root.emojiPopup
|
||||
stickersPopup: root.stickersPopup
|
||||
messageContextMenu: root.messageContextMenu
|
||||
|
||||
chatType: root.messageStore.chatType
|
||||
isEdit: true
|
||||
|
@ -736,8 +705,8 @@ Loader {
|
|||
messageStore: root.messageStore
|
||||
store: root.rootStore
|
||||
isCurrentUser: root.amISender
|
||||
onImageClicked: {
|
||||
root.imageClicked(image);
|
||||
onImageClicked: (image, mouse, imageSource) => {
|
||||
d.onImageClicked(image, mouse, imageSource)
|
||||
}
|
||||
onLinksLoaded: {
|
||||
// If there is only one image and no links, hide the message
|
||||
|
@ -765,7 +734,7 @@ Loader {
|
|||
|
||||
quickActions: [
|
||||
Loader {
|
||||
active: !root.isInPinnedPopup && delegate.hovered && !delegate.hideQuickActions
|
||||
active: d.addReactionAllowed && delegate.hovered && !delegate.hideQuickActions
|
||||
visible: active
|
||||
sourceComponent: StatusFlatRoundButton {
|
||||
width: d.chatButtonSize
|
||||
|
@ -773,9 +742,8 @@ Loader {
|
|||
icon.name: "reaction-b"
|
||||
type: StatusFlatRoundButton.Type.Tertiary
|
||||
tooltip.text: qsTr("Add reaction")
|
||||
onClicked: {
|
||||
if (root.messageClickHandler(delegate, mapToItem(delegate, mouse.x, mouse.y), false, false, false, null, true, false))
|
||||
d.setMessageActive(root.messageId, true)
|
||||
onClicked: (mouse) => {
|
||||
d.addReactionClicked(this, mouse)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -791,9 +759,6 @@ Loader {
|
|||
tooltip.text: qsTr("Reply")
|
||||
onClicked: {
|
||||
root.showReplyArea(root.messageId, root.senderId)
|
||||
if (messageContextMenu.closeParentPopup) {
|
||||
messageContextMenu.closeParentPopup()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -886,12 +851,7 @@ Loader {
|
|||
type: StatusFlatRoundButton.Type.Tertiary
|
||||
tooltip.text: qsTr("Delete")
|
||||
onClicked: {
|
||||
if (!localAccountSensitiveSettings.showDeleteMessageWarning) {
|
||||
messageStore.deleteMessage(root.messageId)
|
||||
}
|
||||
else {
|
||||
Global.openPopup(deleteMessageConfirmationDialogComponent)
|
||||
}
|
||||
messageStore.warnAndDeleteMessage(root.messageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -900,29 +860,6 @@ Loader {
|
|||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: deleteMessageConfirmationDialogComponent
|
||||
|
||||
ConfirmationDialog {
|
||||
confirmButtonObjectName: "chatButtonsPanelConfirmDeleteMessageButton"
|
||||
header.title: qsTr("Confirm deleting this message")
|
||||
confirmationText: qsTr("Are you sure you want to delete this message? Be aware that other clients are not guaranteed to delete the message as well.")
|
||||
height: 260
|
||||
checkbox.visible: true
|
||||
executeConfirm: function () {
|
||||
if (checkbox.checked) {
|
||||
localAccountSensitiveSettings.showDeleteMessageWarning = false
|
||||
}
|
||||
|
||||
close()
|
||||
messageStore.deleteMessage(root.messageId)
|
||||
}
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: newMessagesMarkerComponent
|
||||
|
||||
|
@ -931,4 +868,110 @@ Loader {
|
|||
timestamp: root.messageTimestamp
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: addReactionContextMenu
|
||||
|
||||
MessageAddReactionContextMenu {
|
||||
reactionsModel: root.rootStore.emojiReactionsModel
|
||||
onToggleReaction: (emojiId) => {
|
||||
root.messageStore.toggleReaction(root.messageId, emojiId)
|
||||
}
|
||||
onOpened: {
|
||||
root.setMessageActive(root.messageId, true)
|
||||
}
|
||||
onClosed: {
|
||||
root.setMessageActive(root.messageId, false)
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageContextMenuComponent
|
||||
|
||||
ImageContextMenu {
|
||||
onClosed: {
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: profileContextMenuComponent
|
||||
|
||||
ProfileContextMenu {
|
||||
store: root.rootStore
|
||||
onOpenProfileClicked: (publicKey) => {
|
||||
Global.openProfilePopup(publicKey, null)
|
||||
}
|
||||
onCreateOneToOneChat: (communityId, chatId, ensName) => {
|
||||
Global.changeAppSectionBySectionType(Constants.appSection.chat)
|
||||
root.rootStore.chatCommunitySectionModule.createOneToOneChat("", chatId, ensName)
|
||||
}
|
||||
onOpened: {
|
||||
root.setMessageActive(root.messageId, true)
|
||||
}
|
||||
onClosed: {
|
||||
root.setMessageActive(root.messageId, false)
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: messageContextMenuComponent
|
||||
|
||||
MessageContextMenuView {
|
||||
store: root.rootStore
|
||||
reactionModel: root.rootStore.emojiReactionsModel
|
||||
disabledForChat: !root.rootStore.isUserAllowedToSendMessage
|
||||
|
||||
onPinMessage: (messageId) => {
|
||||
root.messageStore.pinMessage(messageId)
|
||||
}
|
||||
|
||||
onUnpinMessage: (messageId) => {
|
||||
root.messageStore.unpinMessage(messageId)
|
||||
}
|
||||
|
||||
onPinnedMessagesLimitReached: (messageId) => {
|
||||
if (!root.chatContentModule) {
|
||||
console.warn("error on open pinned messages limit reached from message context menu - chat content module is not set")
|
||||
return
|
||||
}
|
||||
Global.openPinnedMessagesPopupRequested(root.rootStore,
|
||||
root.messageStore,
|
||||
root.chatContentModule.pinnedMessagesModel,
|
||||
messageId,
|
||||
root.chatId)
|
||||
}
|
||||
|
||||
onToggleReaction: (messageId, emojiId) => {
|
||||
root.messageStore.toggleReaction(messageId, emojiId)
|
||||
}
|
||||
|
||||
onDeleteMessage: (messageId) => {
|
||||
root.messageStore.warnAndDeleteMessage(messageId)
|
||||
}
|
||||
|
||||
onEditClicked: (messageId) => {
|
||||
root.messageStore.setEditModeOn(messageId)
|
||||
}
|
||||
|
||||
onShowReplyArea: (messageId, senderId) => {
|
||||
root.showReplyArea(messageId, senderId)
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
root.setMessageActive(model.id, true)
|
||||
}
|
||||
onClosed: {
|
||||
root.setMessageActive(model.id, false)
|
||||
destroy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQml.Models 2.3
|
||||
|
||||
import StatusQ.Popups 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
import utils 1.0
|
||||
import shared 1.0
|
||||
import shared.panels 1.0
|
||||
import shared.popups 1.0
|
||||
import shared.status 1.0
|
||||
import shared.controls.chat 1.0
|
||||
import shared.controls.chat.menuItems 1.0
|
||||
|
||||
StatusMenu {
|
||||
id: root
|
||||
|
||||
property var store
|
||||
|
||||
property string myPublicKey: ""
|
||||
|
||||
property string selectedUserPublicKey: ""
|
||||
property string selectedUserDisplayName: ""
|
||||
property string selectedUserIcon: ""
|
||||
|
||||
readonly property bool isMe: {
|
||||
return root.selectedUserPublicKey === root.store.contactsStore.myPublicKey;
|
||||
}
|
||||
readonly property var contactDetails: {
|
||||
if (root.selectedUserPublicKey === "" || isMe) {
|
||||
return {}
|
||||
}
|
||||
return Utils.getContactDetailsAsJson(root.selectedUserPublicKey);
|
||||
}
|
||||
readonly property bool isContact: {
|
||||
return root.selectedUserPublicKey !== "" && !!contactDetails.isContact
|
||||
}
|
||||
readonly property bool isBlockedContact: (!!contactDetails && contactDetails.isBlocked) || false
|
||||
|
||||
readonly property int outgoingVerificationStatus: {
|
||||
if (root.selectedUserPublicKey === "" || root.isMe || !root.isContact) {
|
||||
return 0
|
||||
}
|
||||
return contactDetails.verificationStatus
|
||||
}
|
||||
readonly property int incomingVerificationStatus: {
|
||||
if (root.selectedUserPublicKey === "" || root.isMe || !root.isContact) {
|
||||
return 0
|
||||
}
|
||||
return contactDetails.incomingVerificationStatus
|
||||
}
|
||||
readonly property bool hasPendingContactRequest: {
|
||||
return !root.isMe && root.selectedUserPublicKey !== "" &&
|
||||
root.store.contactsStore.hasPendingContactRequest(root.selectedUserPublicKey);
|
||||
}
|
||||
readonly property bool hasActiveReceivedVerificationRequestFrom: {
|
||||
if (!root.selectedUserPublicKey || root.isMe || !root.isContact) {
|
||||
return false
|
||||
}
|
||||
return contactDetails.incomingVerificationStatus === Constants.verificationStatus.verifying ||
|
||||
contactDetails.incomingVerificationStatus === Constants.verificationStatus.verified
|
||||
}
|
||||
readonly property bool isVerificationRequestSent: {
|
||||
if (!root.selectedUserPublicKey || root.isMe || !root.isContact) {
|
||||
return false
|
||||
}
|
||||
return root.outgoingVerificationStatus !== Constants.verificationStatus.unverified &&
|
||||
root.outgoingVerificationStatus !== Constants.verificationStatus.verified &&
|
||||
root.outgoingVerificationStatus !== Constants.verificationStatus.trusted
|
||||
}
|
||||
readonly property bool isTrusted: {
|
||||
if (!root.selectedUserPublicKey || root.isMe || !root.isContact) {
|
||||
return false
|
||||
}
|
||||
return root.outgoingVerificationStatus === Constants.verificationStatus.trusted ||
|
||||
root.incomingVerificationStatus === Constants.verificationStatus.trusted
|
||||
}
|
||||
|
||||
readonly property bool userTrustIsUnknown: contactDetails && contactDetails.trustStatus === Constants.trustStatus.unknown
|
||||
readonly property bool userIsUntrustworthy: contactDetails && contactDetails.trustStatus === Constants.trustStatus.untrustworthy
|
||||
|
||||
signal openProfileClicked(string publicKey)
|
||||
signal createOneToOneChat(string communityId, string chatId, string ensName)
|
||||
|
||||
onClosed: {
|
||||
// Reset selectedUserPublicKey so that associated properties get recalculated on re-open
|
||||
selectedUserPublicKey = ""
|
||||
}
|
||||
|
||||
width: 230
|
||||
|
||||
ProfileHeader {
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
|
||||
displayNameVisible: false
|
||||
displayNamePlusIconsVisible: true
|
||||
editButtonVisible: false
|
||||
displayName: root.selectedUserDisplayName
|
||||
pubkey: root.selectedUserPublicKey
|
||||
icon: root.selectedUserIcon
|
||||
trustStatus: contactDetails && contactDetails.trustStatus ? contactDetails.trustStatus
|
||||
: Constants.trustStatus.unknown
|
||||
isContact: root.isContact
|
||||
isCurrentUser: root.isMe
|
||||
userIsEnsVerified: (!!contactDetails && contactDetails.ensVerified) || false
|
||||
}
|
||||
|
||||
StatusMenuSeparator {
|
||||
topPadding: root.topPadding
|
||||
}
|
||||
|
||||
ViewProfileMenuItem {
|
||||
id: viewProfileAction
|
||||
onTriggered: {
|
||||
root.openProfileClicked(root.selectedUserPublicKey)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
SendMessageMenuItem {
|
||||
id: sendMessageMenuItem
|
||||
enabled: root.isContact && !root.isBlockedContact
|
||||
onTriggered: {
|
||||
root.createOneToOneChat("", root.selectedUserPublicKey, "")
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
SendContactRequestMenuItem {
|
||||
id: sendContactRequestMenuItem
|
||||
enabled: !root.isMe && !root.isContact
|
||||
&& !root.isBlockedContact && !root.hasPendingContactRequest
|
||||
onTriggered: {
|
||||
Global.openContactRequestPopup(root.selectedUserPublicKey, null)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: verifyIdentityAction
|
||||
text: qsTr("Verify Identity")
|
||||
icon.name: "checkmark-circle"
|
||||
enabled: !root.isMe && root.isContact
|
||||
&& !root.isBlockedContact
|
||||
&& root.outgoingVerificationStatus === Constants.verificationStatus.unverified
|
||||
&& !root.hasActiveReceivedVerificationRequestFrom
|
||||
onTriggered: {
|
||||
Global.openSendIDRequestPopup(root.selectedUserPublicKey, null)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: pendingIdentityAction
|
||||
text: isVerificationRequestSent ||
|
||||
root.incomingVerificationStatus === Constants.verificationStatus.verified ?
|
||||
qsTr("ID Request Pending....") :
|
||||
qsTr("Respond to ID Request...")
|
||||
icon.name: "checkmark-circle"
|
||||
enabled: !root.isMe && root.isContact
|
||||
&& !root.isBlockedContact && !root.isTrusted
|
||||
&& (root.hasActiveReceivedVerificationRequestFrom
|
||||
|| root.isVerificationRequestSent)
|
||||
onTriggered: {
|
||||
if (hasActiveReceivedVerificationRequestFrom) {
|
||||
Global.openIncomingIDRequestPopup(root.selectedUserPublicKey, null)
|
||||
} else if (root.isVerificationRequestSent) {
|
||||
Global.openOutgoingIDRequestPopup(root.selectedUserPublicKey, null)
|
||||
}
|
||||
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: renameAction
|
||||
text: qsTr("Rename")
|
||||
icon.name: "edit_pencil"
|
||||
enabled: !root.isMe
|
||||
onTriggered: {
|
||||
Global.openNicknamePopupRequested(root.selectedUserPublicKey, contactDetails.localNickname,
|
||||
"%1 (%2)".arg(root.selectedUserDisplayName).arg(Utils.getElidedCompressedPk(root.selectedUserPublicKey)))
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: unblockAction
|
||||
text: qsTr("Unblock User")
|
||||
icon.name: "remove-circle"
|
||||
enabled: !root.isMe && root.isBlockedContact
|
||||
onTriggered: Global.unblockContactRequested(root.selectedUserPublicKey, root.selectedUserDisplayName)
|
||||
}
|
||||
|
||||
StatusMenuSeparator {
|
||||
visible: blockMenuItem.enabled
|
||||
|| markUntrustworthyMenuItem.enabled
|
||||
|| removeUntrustworthyMarkMenuItem.enabled
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: markUntrustworthyMenuItem
|
||||
text: qsTr("Mark as Untrustworthy")
|
||||
icon.name: "warning"
|
||||
type: StatusAction.Type.Danger
|
||||
enabled: !root.isMe && root.userTrustIsUnknown
|
||||
onTriggered: root.store.contactsStore.markUntrustworthy(root.selectedUserPublicKey)
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: removeUntrustworthyMarkMenuItem
|
||||
text: qsTr("Remove Untrustworthy Mark")
|
||||
icon.name: "warning"
|
||||
enabled: !root.isMe && root.userIsUntrustworthy
|
||||
onTriggered: root.store.contactsStore.removeTrustStatus(root.selectedUserPublicKey)
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
text: qsTr("Remove Contact")
|
||||
icon.name: "remove-contact"
|
||||
type: StatusAction.Type.Danger
|
||||
enabled: root.isContact && !root.isBlockedContact && !root.hasPendingContactRequest
|
||||
onTriggered: {
|
||||
Global.removeContactRequested(root.selectedUserDisplayName, root.selectedUserPublicKey)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StatusAction {
|
||||
id: blockMenuItem
|
||||
text: qsTr("Block User")
|
||||
icon.name: "cancel"
|
||||
type: StatusAction.Type.Danger
|
||||
enabled: !root.isMe && !root.isBlockedContact
|
||||
onTriggered: Global.blockContactRequested(root.selectedUserPublicKey, root.selectedUserDisplayName)
|
||||
}
|
||||
|
||||
}
|
|
@ -10,3 +10,6 @@ ProfileHeaderContextMenuView 1.0 ProfileHeaderContextMenuView.qml
|
|||
TransactionBubbleView 1.0 TransactionBubbleView.qml
|
||||
NewMessagesMarker 1.0 NewMessagesMarker.qml
|
||||
SimplifiedMessageView 1.0 SimplifiedMessageView.qml
|
||||
ImageContextMenu 1.0 ImageContextMenu.qml
|
||||
ProfileContextMenu 1.0 ProfileContextMenu.qml
|
||||
MessageAddReactionContextMenu 1.0 MessageAddReactionContextMenu.qml
|
||||
|
|
|
@ -32,7 +32,7 @@ QtObject {
|
|||
signal openDownloadModalRequested(bool available, string version, string url)
|
||||
signal openChangeProfilePicPopup(var cb)
|
||||
signal openBackUpSeedPopup()
|
||||
signal openImagePopup(var image, var contextMenu)
|
||||
signal openImagePopup(var image)
|
||||
signal openProfilePopupRequested(string publicKey, var parentPopup)
|
||||
signal openEditDisplayNamePopup()
|
||||
signal openActivityCenterPopupRequested()
|
||||
|
@ -42,6 +42,8 @@ QtObject {
|
|||
signal openInviteFriendsToCommunityPopup(var community, var communitySectionModule, var cb)
|
||||
signal openIncomingIDRequestPopup(string publicKey, var cb)
|
||||
signal openOutgoingIDRequestPopup(string publicKey, var cb)
|
||||
signal openDeleteMessagePopup(string messageId, var messageStore)
|
||||
signal openDownloadImageDialog(string imageSource)
|
||||
signal contactRenamed(string publicKey)
|
||||
|
||||
signal openLink(string link)
|
||||
|
@ -77,4 +79,13 @@ QtObject {
|
|||
function changeAppSectionBySectionType(sectionType, subsection = 0) {
|
||||
root.appSectionBySectionTypeChanged(sectionType, subsection);
|
||||
}
|
||||
|
||||
function openMenu(menuComponent, menuParent, params = {}, point = undefined) {
|
||||
const menu = menuComponent.createObject(menuParent, params)
|
||||
if (point)
|
||||
menu.popup(point)
|
||||
else
|
||||
menu.popup()
|
||||
return menu
|
||||
}
|
||||
}
|
||||
|
|
|
@ -678,6 +678,14 @@ QtObject {
|
|||
return text
|
||||
}
|
||||
|
||||
function copyImageToClipboardByUrl(content) {
|
||||
globalUtilsInst.copyImageToClipboardByUrl(content)
|
||||
}
|
||||
|
||||
function downloadImageByUrl(url, path) {
|
||||
globalUtilsInst.downloadImageByUrl(url, path)
|
||||
}
|
||||
|
||||
// Leave this function at the bottom of the file as QT Creator messes up the code color after this
|
||||
function isPunct(c) {
|
||||
return /(!|\@|#|\$|%|\^|&|\*|\(|\)|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)
|
||||
|
|
Loading…
Reference in New Issue