diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 6a8384230..e0fa5f494 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -35,6 +35,8 @@ qt_add_library(neochat STATIC models/commonroomsmodel.h texttospeechhelper.h texttospeechhelper.cpp + models/limitermodel.cpp + models/limitermodel.h ) set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES diff --git a/src/app/models/commonroomsmodel.cpp b/src/app/models/commonroomsmodel.cpp index c355f30d4..1fe05853d 100644 --- a/src/app/models/commonroomsmodel.cpp +++ b/src/app/models/commonroomsmodel.cpp @@ -5,6 +5,7 @@ #include "jobs/neochatgetcommonroomsjob.h" #include +#include using namespace Quotient; @@ -39,8 +40,22 @@ void CommonRoomsModel::setUserId(const QString &userId) QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const { - Q_UNUSED(index) - Q_UNUSED(roleName) + auto roomId = m_commonRooms[index.row()]; + auto room = connection()->room(roomId); + if (!room) { + return {}; + } + + switch (roleName) { + case Qt::DisplayRole: + case RoomNameRole: + return room->displayName(); + case RoomAvatarRole: + return room->avatarUrl(); + case RoomIdRole: + return roomId; + } + return {}; } @@ -50,6 +65,15 @@ int CommonRoomsModel::rowCount(const QModelIndex &parent) const return m_commonRooms.size(); } +QHash CommonRoomsModel::roleNames() const +{ + return { + {RoomIdRole, "roomId"}, + {RoomNameRole, "roomName"}, + {RoomAvatarRole, "roomAvatar"}, + }; +} + void CommonRoomsModel::reload() { if (!m_connection || m_userId.isEmpty()) { diff --git a/src/app/models/commonroomsmodel.h b/src/app/models/commonroomsmodel.h index f0c8cc068..b6afc060a 100644 --- a/src/app/models/commonroomsmodel.h +++ b/src/app/models/commonroomsmodel.h @@ -24,7 +24,9 @@ class CommonRoomsModel : public QAbstractListModel public: enum Roles { - RoomIdRole = Qt::DisplayRole, + RoomIdRole = Qt::UserRole, + RoomNameRole, + RoomAvatarRole, }; Q_ENUM(Roles) @@ -39,6 +41,8 @@ public: [[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override; [[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override; + QHash roleNames() const override; + Q_SIGNALS: void connectionChanged(); void userIdChanged(); diff --git a/src/app/models/limitermodel.cpp b/src/app/models/limitermodel.cpp new file mode 100644 index 000000000..c5834eef7 --- /dev/null +++ b/src/app/models/limitermodel.cpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include "models/limitermodel.h" + +LimiterModel::LimiterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + connect(this, &QSortFilterProxyModel::rowsInserted, this, &LimiterModel::extraCountChanged); + connect(this, &QSortFilterProxyModel::rowsRemoved, this, &LimiterModel::extraCountChanged); + connect(this, &QSortFilterProxyModel::modelReset, this, &LimiterModel::extraCountChanged); +} + +int LimiterModel::maximumCount() const +{ + return m_maximumCount; +} + +void LimiterModel::setMaximumCount(int maximumCount) +{ + if (m_maximumCount != maximumCount) { + m_maximumCount = maximumCount; + Q_EMIT maximumCountChanged(); + } +} + +int LimiterModel::extraCount() const +{ + if (sourceModel()) { + return std::max(sourceModel()->rowCount() - maximumCount(), 0); + } + return 0; +} + +bool LimiterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + return source_row < maximumCount(); +} + +#include "moc_limitermodel.cpp" diff --git a/src/app/models/limitermodel.h b/src/app/models/limitermodel.h new file mode 100644 index 000000000..b05db805f --- /dev/null +++ b/src/app/models/limitermodel.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#pragma once + +#include +#include + +/** + * @class LimiterModel + * + * @brief Takes a source QAbstractItemModel model and only displays a desired maximum amount. + * + * Also gives you the remaining (filtered out) items, useful for sticking in a label or somesuch. + */ +class LimiterModel : public QSortFilterProxyModel +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged) + Q_PROPERTY(int extraCount READ extraCount NOTIFY extraCountChanged) + +public: + explicit LimiterModel(QObject *parent = nullptr); + + [[nodiscard]] int maximumCount() const; + void setMaximumCount(int maximumCount); + + [[nodiscard]] int extraCount() const; + +Q_SIGNALS: + void maximumCountChanged(); + void extraCountChanged(); + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +private: + int m_maximumCount = 0; +}; diff --git a/src/app/qml/UserDetailDialog.qml b/src/app/qml/UserDetailDialog.qml index 8c2b9e852..7ab139c0d 100644 --- a/src/app/qml/UserDetailDialog.qml +++ b/src/app/qml/UserDetailDialog.qml @@ -23,27 +23,43 @@ Kirigami.Dialog { property NeoChatConnection connection - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 + property CommonRoomsModel model: CommonRoomsModel { + connection: root.connection + userId: root.user.id + } + + property LimiterModel limiterModel: LimiterModel { + maximumCount: 5 + sourceModel: root.model + } + + readonly property bool isSelf: root.user.id === root.connection.localUserId + readonly property bool hasMutualRooms: root.model.count > 0 + readonly property bool isRoomProfile: root.room + readonly property string shareUrl: "https://matrix.to/#/" + root.user.id + + leftPadding: Kirigami.Units.largeSpacing * 2 + rightPadding: Kirigami.Units.largeSpacing * 2 + topPadding: Kirigami.Units.largeSpacing * 2 + bottomPadding: Kirigami.Units.largeSpacing * 2 standardButtons: Kirigami.Dialog.NoButton width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24) title: i18nc("@title:menu Account details dialog", "Account Details") + header: null + contentItem: ColumnLayout { spacing: 0 + RowLayout { id: detailRow - Layout.fillWidth: true - Layout.leftMargin: Kirigami.Units.largeSpacing - Layout.rightMargin: Kirigami.Units.largeSpacing - Layout.topMargin: Kirigami.Units.largeSpacing - Layout.bottomMargin: Kirigami.Units.largeSpacing + spacing: Kirigami.Units.largeSpacing + Layout.fillWidth: true + KirigamiComponents.Avatar { id: avatar Layout.preferredWidth: Kirigami.Units.iconSizes.huge @@ -75,242 +91,285 @@ Kirigami.Dialog { id: idLabel textFormat: TextEdit.PlainText text: idLabelTextMetrics.elidedText + color: Kirigami.Theme.disabledTextColor TextMetrics { id: idLabelTextMetrics text: root.user.id elide: Qt.ElideRight - elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin + elideWidth: root.availableWidth - avatar.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin } } - QQC2.Label { - property CommonRoomsModel model: CommonRoomsModel { - connection: root.connection - userId: root.user.id - } - - text: i18ncp("@info", "One mutual room", "%1 mutual rooms", model.count) - color: Kirigami.Theme.disabledTextColor - visible: model.count > 0 - + Kirigami.ActionToolBar { Layout.topMargin: Kirigami.Units.smallSpacing + + actions: [ + Kirigami.Action { + text: i18nc("@action:intoolbar Message this user directly", "Message") + icon.name: "document-send-symbolic" + + onTriggered: { + root.close(); + root.connection.requestDirectChat(root.user.id); + } + }, + Kirigami.Action { + icon.name: "im-invisible-user-symbolic" + text: root.connection.isIgnored(root.user.id) ? i18nc("@action:intoolbar Unignore or 'unblock' this user", "Unignore") : i18nc("@action:intoolbar Ignore or 'block' this user", "Ignore") + + onTriggered: { + root.close(); + root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id); + } + }, + Kirigami.Action { + text: i18nc("@action:intoolbar Copy shareable link for this user", "Copy Link") + icon.name: "username-copy-symbolic" + + onTriggered: Clipboard.saveText(root.shareUrl) + }, + Kirigami.Action { + text: i18nc("@action:intoolbar Search for this user's messages.", "Search Messages…") + icon.name: "search-symbolic" + + onTriggered: { + ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), { + room: root.room, + senderId: root.user.id + }, { + title: i18nc("@action:title", "Search") + }); + root.close(); + } + }, + Kirigami.Action { + text: i18nc("@action:intoolbar", "Show QR Code") + icon.name: "view-barcode-qr-symbolic" + + onTriggered: { + let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, { + text: root.shareUrl, + title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName, + subtitle: root.user.id, + avatarColor: root.room?.member(root.user.id).color, + avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl + }) as QrCodeMaximizeComponent; + root.close(); + qrCode.open(); + } + }, + Kirigami.Action { + text: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report…") + icon.name: "dialog-warning-symbolic" + visible: root.connection.supportsMatrixSpecVersion("v1.13") + + onTriggered: { + let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { + title: i18nc("@title:dialog", "Report User"), + placeholder: i18nc("@info:placeholder", "Reason for reporting this user"), + icon: "dialog-warning-symbolic", + actionText: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report") + }, { + title: i18nc("@title", "Report User"), + width: Kirigami.Units.gridUnit * 25 + }) as ReasonDialog; + dialog.accepted.connect(reason => { + root.connection.reportUser(root.user.id, reason); + }); + } + } + ] } } - QQC2.AbstractButton { - id: qrButton - Layout.minimumHeight: avatar.height * 0.75 - Layout.maximumHeight: avatar.height * 1.5 - Layout.maximumWidth: avatar.height * 1.5 + } - contentItem: Barcode { - id: barcode - barcodeType: Barcode.QRCode - content: "https://matrix.to/#/" + root.user.id - } + Kirigami.Heading { + text: i18nc("@title Moderation actions for this user", "Moderation") + level: 2 + visible: root.isRoomProfile && moderationToolbar.actions.filter(function (it) { return it.visible; }).length > 0 - onClicked: { - let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, { - text: barcode.content, - title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName, - subtitle: root.user.id, - avatarColor: root.room?.member(root.user.id).color, - avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl - }) as QrCodeMaximizeComponent; - root.close(); - qrCode.open(); + Layout.topMargin: Kirigami.Units.largeSpacing + } + + Kirigami.ActionToolBar { + id: moderationToolbar + + flat: false + visible: root.isRoomProfile + + Layout.fillWidth: true + Layout.topMargin: Kirigami.Units.smallSpacing + + actions: [ + Kirigami.Action { + visible: !root.isSelf && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId) + + text: i18nc("@action:button Kick the user from the room", "Kick…") + icon.name: "im-kick-user" + onTriggered: { + let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { + title: i18nc("@title:dialog", "Kick User"), + placeholder: i18nc("@info:placeholder", "Reason for kicking this user"), + actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"), + icon: "im-kick-user" + }, { + title: i18nc("@title:dialog", "Kick User"), + width: Kirigami.Units.gridUnit * 25 + }); + dialog.accepted.connect(reason => { + root.room.kickMember(root.user.id, reason); + }); + root.close(); + } + }, + Kirigami.Action { + visible: !root.isSelf && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId) + + text: i18nc("@action:button Ban this user from the room", "Ban…") + icon.name: "im-ban-user" + icon.color: Kirigami.Theme.negativeTextColor + onTriggered: { + let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { + title: i18nc("@title:dialog", "Ban User"), + placeholder: i18nc("@info:placeholder", "Reason for banning this user"), + actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"), + icon: "im-ban-user" + }, { + title: i18nc("@title:dialog", "Ban User"), + width: Kirigami.Units.gridUnit * 25 + }); + dialog.accepted.connect(reason => { + root.room.ban(root.user.id, reason); + }); + root.close(); + } + }, + Kirigami.Action { + visible: !root.isSelf && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id) + + text: i18nc("@action:button Unban the user from this room", "Unban") + icon.name: "im-irc" + icon.color: Kirigami.Theme.negativeTextColor + onTriggered: { + root.room.unban(root.user.id); + root.close(); + } + }, + Kirigami.Action { + visible: (root.user.id === root.connection.localUserId || root.room.canSendState("redact")) + + text: i18nc("@action:button Remove messages from the user in this room", "Remove Messages…") + icon.name: "delete" + icon.color: Kirigami.Theme.negativeTextColor + onTriggered: { + let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { + title: i18nc("@title:dialog", "Remove Messages"), + placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"), + actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"), + icon: "delete" + }, { + title: i18nc("@title", "Remove Messages"), + width: Kirigami.Units.gridUnit * 25 + }); + dialog.accepted.connect(reason => { + root.room.deleteMessagesByUser(root.user.id, reason); + }); + root.close(); + } } + ] + } + + Kirigami.Heading { + text: i18nc("@title Role such as 'Admin' or 'Moderator' for this user", "Role") + level: 2 + visible: root.isRoomProfile + + Layout.topMargin: Kirigami.Units.largeSpacing + } + + RowLayout { + spacing: Kirigami.Units.smallSpacing + visible: root.isRoomProfile + + Layout.topMargin: Kirigami.Units.smallSpacing + + QQC2.Label { + text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : "" + } + + QQC2.Button { + visible: root.room.canSendState("m.room.power_levels") + text: i18nc("@action:button Set the power level (such as 'Admin') for this user", "Set Power Level") + icon.name: "document-edit-symbolic" + display: QQC2.AbstractButton.IconOnly QQC2.ToolTip.visible: hovered - QQC2.ToolTip.text: barcode.content + QQC2.ToolTip.text: text QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - } - } - Kirigami.Chip { - visible: root.room - text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : "" - closable: false - checkable: false + onClicked: { + (powerLevelDialog.createObject(this, { + room: root.room, + userId: root.user.id, + powerLevel: root.room.memberEffectivePowerLevel(root.user.id) + }) as PowerLevelDialog).open(); + root.close(); + } - Layout.leftMargin: Kirigami.Units.largeSpacing - Layout.bottomMargin: Kirigami.Units.largeSpacing - } - - Kirigami.Separator { - Layout.fillWidth: true - } - - FormCard.FormButtonDelegate { - visible: root.user.id !== root.connection.localUserId && !!root.user - text: !!root.user && root.connection.isIgnored(root.user.id) ? i18n("Unignore this user") : i18n("Ignore this user") - icon.name: "im-invisible-user" - onClicked: { - root.close(); - root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id); - } - } - - FormCard.FormButtonDelegate { - visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId) - - text: i18nc("@action:button", "Kick this user") - icon.name: "im-kick-user" - onClicked: { - let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { - title: i18nc("@title:dialog", "Kick User"), - placeholder: i18nc("@info:placeholder", "Reason for kicking this user"), - actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"), - icon: "im-kick-user" - }, { - title: i18nc("@title:dialog", "Kick User"), - width: Kirigami.Units.gridUnit * 25 - }); - dialog.accepted.connect(reason => { - root.room.kickMember(root.user.id, reason); - }); - root.close(); - } - } - - FormCard.FormButtonDelegate { - visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("invite") && !root.room.containsUser(root.user.id) - - enabled: root.room && !root.room.isUserBanned(root.user.id) - text: i18nc("@action:button", "Invite this user") - icon.name: "list-add-user" - onClicked: { - root.room.inviteToRoom(root.user.id); - root.close(); - } - } - - FormCard.FormButtonDelegate { - visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId) - - text: i18nc("@action:button", "Ban this user") - icon.name: "im-ban-user" - icon.color: Kirigami.Theme.negativeTextColor - onClicked: { - let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { - title: i18nc("@title:dialog", "Ban User"), - placeholder: i18nc("@info:placeholder", "Reason for banning this user"), - actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"), - icon: "im-ban-user" - }, { - title: i18nc("@title:dialog", "Ban User"), - width: Kirigami.Units.gridUnit * 25 - }); - dialog.accepted.connect(reason => { - root.room.ban(root.user.id, reason); - }); - root.close(); - } - } - - FormCard.FormButtonDelegate { - visible: root.room && root.user.id !== root.connection.localUserId && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id) - - text: i18nc("@action:button", "Unban this user") - icon.name: "im-irc" - icon.color: Kirigami.Theme.negativeTextColor - onClicked: { - root.room.unban(root.user.id); - root.close(); - } - } - - FormCard.FormButtonDelegate { - visible: root.room && root.room.canSendState("m.room.power_levels") - text: i18nc("@action:button", "Set user power level") - icon.name: "visibility" - onClicked: { - (powerLevelDialog.createObject(this, { - room: root.room, - userId: root.user.id, - powerLevel: root.room.memberEffectivePowerLevel(root.user.id) - }) as PowerLevelDialog).open(); - root.close(); - } - - Component { - id: powerLevelDialog - PowerLevelDialog { + Component { id: powerLevelDialog + PowerLevelDialog { + id: powerLevelDialog + } } } } - FormCard.FormButtonDelegate { - visible: root.room && (root.user.id === root.connection.localUserId || root.room.canSendState("redact")) + Kirigami.Heading { + text: i18nc("@title The set of common rooms between your current user and the one shown", "Mutual Rooms") + level: 4 + visible: !root.isSelf && root.hasMutualRooms - text: i18nc("@action:button", "Remove recent messages by this user") - icon.name: "delete" - icon.color: Kirigami.Theme.negativeTextColor - onClicked: { - let dialog = ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { - title: i18nc("@title:dialog", "Remove Messages"), - placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"), - actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"), - icon: "delete" - }, { - title: i18nc("@title", "Remove Messages"), - width: Kirigami.Units.gridUnit * 25 - }); - dialog.accepted.connect(reason => { - root.room.deleteMessagesByUser(root.user.id, reason); - }); - root.close(); + Layout.topMargin: Kirigami.Units.largeSpacing + } + + RowLayout { + spacing: Kirigami.Units.smallSpacing + visible: !root.isSelf && root.hasMutualRooms + + Layout.topMargin: Kirigami.Units.smallSpacing + + Repeater { + model: root.limiterModel + + delegate: KirigamiComponents.AvatarButton { + required property string roomName + required property string roomAvatar + required property string roomId + + Layout.preferredWidth: Kirigami.Units.iconSizes.medium + Layout.preferredHeight: Kirigami.Units.iconSizes.medium + + name: roomName + source: roomAvatar + + onClicked: { + root.close(); + RoomManager.resolveResource(roomId); + } + + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.text: name + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } } - } - FormCard.FormButtonDelegate { - visible: root.user.id !== root.connection.localUserId - text: root.connection.directChatExists(root.user) ? i18nc("%1 is the name of the user.", "Chat with %1", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName)) : i18n("Invite to private chat") - icon.name: "document-send" - onClicked: { - root.connection.requestDirectChat(root.user.id); - root.close(); - } - } - - FormCard.FormButtonDelegate { - text: i18nc("@action:button %1 is the name of the user.", "Search room for %1's messages", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName)) - icon.name: "search-symbolic" - onClicked: { - ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), { - room: root.room, - senderId: root.user.id - }, { - title: i18nc("@action:title", "Search") - }); - root.close(); - } - } - - FormCard.FormButtonDelegate { - text: i18n("Copy link") - icon.name: "username-copy" - onClicked: Clipboard.saveText("https://matrix.to/#/" + root.user.id) - } - - FormCard.FormButtonDelegate { - text: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report…") - icon.name: "dialog-warning-symbolic" - visible: root.connection.supportsMatrixSpecVersion("v1.13") - onClicked: { - let dialog = ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { - title: i18nc("@title:dialog", "Report User"), - placeholder: i18nc("@info:placeholder", "Reason for reporting this user"), - icon: "dialog-warning-symbolic", - actionText: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report") - }, { - title: i18nc("@title", "Report User"), - width: Kirigami.Units.gridUnit * 25 - }) as ReasonDialog; - dialog.accepted.connect(reason => { - root.connection.reportUser(root.user.id, reason); - }); + QQC2.Label { + text: i18ncp("@info:label And '%1' more rooms you have in common with this user, but are not shown", "and 1 more…", "and %1 more…", root.limiterModel.extraCount) + visible: root.limiterModel.extraCount > 0 + color: Kirigami.Theme.disabledTextColor } } }