Compare commits
36 Commits
work/redst
...
work/tobia
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
371be1511f | ||
|
|
04472dae4f | ||
|
|
aa40fc84ea | ||
|
|
24e43d063a | ||
|
|
c5caffcdf9 | ||
|
|
95d334ad86 | ||
|
|
602ac5c55f | ||
|
|
247423bf83 | ||
|
|
24d35b3eae | ||
|
|
8bcd9f7469 | ||
|
|
edf5d55da4 | ||
|
|
976af783e2 | ||
|
|
d87954838e | ||
|
|
e757331dce | ||
|
|
bf4f6f5728 | ||
|
|
c73bc8fc29 | ||
|
|
211a08db68 | ||
|
|
38987e6d4c | ||
|
|
9d76e7e30b | ||
|
|
4c1a8d3657 | ||
|
|
7a5de25885 | ||
|
|
a17aa2c6fa | ||
|
|
207a7876b6 | ||
|
|
4c638a740e | ||
|
|
0ee89e1b2b | ||
|
|
4af42a57f4 | ||
|
|
34f2c2dabc | ||
|
|
9ff942915a | ||
|
|
10123abc5b | ||
|
|
ad993d4340 | ||
|
|
ddc0a66d5b | ||
|
|
e8981bdc0f | ||
|
|
c42486a061 | ||
|
|
64d82b8d2a | ||
|
|
677abee890 | ||
|
|
3a25a62350 |
@@ -130,7 +130,8 @@ void EventHandlerTest::timeString()
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, event, true),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toString(u"hh:mm"_s));
|
||||
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s),
|
||||
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::LocalTime)).toString(u"hh:mm"_s));
|
||||
|
||||
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
|
||||
QCOMPARE(room->pendingEvents().size(), 1);
|
||||
|
||||
561
po/ar/neochat.po
561
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
499
po/az/neochat.po
499
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
504
po/ca/neochat.po
504
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
557
po/cs/neochat.po
557
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
565
po/da/neochat.po
565
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
528
po/de/neochat.po
528
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
520
po/el/neochat.po
520
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
528
po/eo/neochat.po
528
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
504
po/es/neochat.po
504
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
617
po/eu/neochat.po
617
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
553
po/fi/neochat.po
553
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
589
po/fr/neochat.po
589
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
536
po/gl/neochat.po
536
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
511
po/he/neochat.po
511
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
528
po/hi/neochat.po
528
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
954
po/hu/neochat.po
954
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
532
po/ia/neochat.po
532
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
515
po/id/neochat.po
515
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
503
po/ie/neochat.po
503
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
510
po/it/neochat.po
510
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
503
po/ja/neochat.po
503
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
509
po/ka/neochat.po
509
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
585
po/ko/neochat.po
585
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
503
po/lt/neochat.po
503
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
536
po/lv/neochat.po
536
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
513
po/nl/neochat.po
513
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
510
po/nn/neochat.po
510
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
496
po/pa/neochat.po
496
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
528
po/pl/neochat.po
528
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
515
po/pt/neochat.po
515
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
529
po/ru/neochat.po
529
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
528
po/sa/neochat.po
528
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
499
po/sk/neochat.po
499
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
517
po/sl/neochat.po
517
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
536
po/sv/neochat.po
536
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
528
po/ta/neochat.po
528
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
509
po/tr/neochat.po
509
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
508
po/uk/neochat.po
508
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -106,7 +106,7 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
}
|
||||
|
||||
QQC2.Action {
|
||||
text: i18n("Logout")
|
||||
text: i18n("Logout…")
|
||||
icon.name: "im-kick-user"
|
||||
onTriggered: confirmLogoutDialogComponent.createObject(root).open()
|
||||
}
|
||||
|
||||
@@ -52,6 +52,15 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Kirigami.SelectableLabel {
|
||||
Layout.fillWidth: true
|
||||
font: Kirigami.Theme.smallFont
|
||||
textFormat: TextEdit.PlainText
|
||||
visible: root.currentRoom && root.currentRoom.canonicalAlias
|
||||
text: root.currentRoom && root.currentRoom.canonicalAlias ? root.currentRoom.canonicalAlias : ""
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
text: root.currentRoom.displayName
|
||||
|
||||
@@ -70,7 +79,14 @@ ColumnLayout {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
text: root.currentRoom.displayName
|
||||
text: root.invitingMember.displayName
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
text: root.invitingMember.id
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
@@ -159,7 +175,7 @@ ColumnLayout {
|
||||
|
||||
QQC2.Label {
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
text: i18nc("@info:label", "You can reject invitations from unknown users under Security settings.")
|
||||
text: xi18nc("@info:label Ensure you are referring to the same translation used for that settings page", "You can reject invitations from unknown users under the <interface>Security & Safety</interface> settings.")
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
// + 5 to prevent it from wrapping unnecessarily
|
||||
|
||||
@@ -47,7 +47,7 @@ Kirigami.Page {
|
||||
icon.name: "document-edit"
|
||||
visible: root.allowEdit
|
||||
enabled: room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog.qml"), {
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog"), {
|
||||
room: root.room,
|
||||
type: root.type,
|
||||
stateKey: root.stateKey,
|
||||
|
||||
@@ -236,11 +236,18 @@ void RoomManager::resolveResource(Uri uri, const QString &action)
|
||||
}
|
||||
}
|
||||
|
||||
void RoomManager::maximizeMedia(int index)
|
||||
void RoomManager::maximizeMedia(const QString &eventId)
|
||||
{
|
||||
if (index < -1 || index > m_mediaMessageFilterModel->rowCount()) {
|
||||
if (eventId.isEmpty()) {
|
||||
qWarning() << "Tried to open media for empty event id";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = m_mediaMessageFilterModel->getRowForEventId(eventId);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_EMIT showMaximizedMedia(index);
|
||||
}
|
||||
|
||||
@@ -397,7 +404,9 @@ void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAli
|
||||
|
||||
// If no one gives us a homeserver suggestion, try the server specified in the alias/id.
|
||||
// Otherwise joining a remote room not on our homeserver will fail.
|
||||
if (vias.empty()) {
|
||||
// This is a hack and we're not supposed to do it. With room ids not containing the server going forward, it won't work anymore for new room versions.
|
||||
// FIXME: Let's keep it around anyway for now, remove it at some point, though
|
||||
if (vias.empty() && roomAliasOrId.contains(':'_L1)) {
|
||||
vias.append(roomAliasOrId.mid(roomAliasOrId.lastIndexOf(':'_L1) + 1));
|
||||
}
|
||||
|
||||
|
||||
@@ -212,12 +212,8 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Show a media item maximized.
|
||||
*
|
||||
* @param index the index to open the maximize delegate model at. This is the
|
||||
* index in the MediaMessageFilterModel owned by this RoomManager. A value
|
||||
* of -1 opens a the default item.
|
||||
*/
|
||||
Q_INVOKABLE void maximizeMedia(int index);
|
||||
Q_INVOKABLE void maximizeMedia(const QString &eventId);
|
||||
|
||||
Q_INVOKABLE void maximizeCode(NeochatRoomMember *author, const QDateTime &time, const QString &codeText, const QString &language);
|
||||
|
||||
|
||||
@@ -70,13 +70,23 @@ public:
|
||||
*
|
||||
* @param event the event to return a type for.
|
||||
*
|
||||
* @param isInReply whether this event is to be treated like a replied-to event (i.e., a basic text fallback should be shown if no other type is used)
|
||||
*
|
||||
* @sa Type
|
||||
*/
|
||||
static Type typeForEvent(const Quotient::RoomEvent &event)
|
||||
static Type typeForEvent(const Quotient::RoomEvent &event, bool isInReply = false)
|
||||
{
|
||||
using namespace Quotient;
|
||||
|
||||
if (event.isRedacted()) {
|
||||
return MessageComponentType::Text;
|
||||
}
|
||||
|
||||
if (const auto e = eventCast<const RoomMessageEvent>(&event)) {
|
||||
if (e->rawMsgtype() == u"m.key.verification.request"_s) {
|
||||
return MessageComponentType::Verification;
|
||||
}
|
||||
|
||||
switch (e->msgtype()) {
|
||||
case MessageEventType::Emote:
|
||||
return MessageComponentType::Text;
|
||||
@@ -103,7 +113,8 @@ public:
|
||||
if (event.matrixType() == u"org.matrix.msc3672.beacon_info"_s) {
|
||||
return MessageComponentType::LiveLocation;
|
||||
}
|
||||
return MessageComponentType::Other;
|
||||
// In the (unlikely) case that this is a reply to a state event, we do want to show something
|
||||
return isInReply ? MessageComponentType::Text : MessageComponentType::Other;
|
||||
}
|
||||
if (is<const EncryptedEvent>(event)) {
|
||||
return MessageComponentType::Encrypted;
|
||||
@@ -116,7 +127,8 @@ public:
|
||||
return MessageComponentType::Poll;
|
||||
}
|
||||
|
||||
return MessageComponentType::Other;
|
||||
// In the (unlikely) case that this is a reply to an unusual event, we do want to show something
|
||||
return isInReply ? MessageComponentType::Text : MessageComponentType::Other;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -448,6 +448,12 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
||||
[](const PollStartEvent &e) {
|
||||
return e.question();
|
||||
},
|
||||
[](const EncryptedEvent &) {
|
||||
return i18nc("@info In room list", "Encrypted event");
|
||||
},
|
||||
[](const ReactionEvent &e) {
|
||||
return i18nc("[user] reacted with <emoji>", "reacted with %1", e.key());
|
||||
},
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
|
||||
|
||||
@@ -31,13 +31,7 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18n("Leaving this room."));
|
||||
room->forget();
|
||||
} else {
|
||||
QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
// FIXME: re-add sanity check for roomId/alias
|
||||
auto leaving = dynamic_cast<NeoChatRoom *>(room->connection()->room(text));
|
||||
if (!leaving) {
|
||||
leaving = dynamic_cast<NeoChatRoom *>(room->connection()->roomByAlias(text));
|
||||
@@ -217,13 +211,7 @@ QList<ActionsModel::Action> actions{
|
||||
Action{
|
||||
u"join"_s,
|
||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
// FIXME: re-add sanity check for roomId/alias
|
||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||
if (targetRoom) {
|
||||
ActionsModel::instance().resolveResource(targetRoom->id());
|
||||
@@ -242,25 +230,18 @@ QList<ActionsModel::Action> actions{
|
||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
auto parts = text.split(u" "_s);
|
||||
QString roomName = parts[0];
|
||||
QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
|
||||
auto regexMatch = roomRegex.match(roomName);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||
if (targetRoom) {
|
||||
// FIXME: re-add sanity check for roomId/alias
|
||||
if (const auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text)) {
|
||||
ActionsModel::instance().resolveResource(targetRoom->id());
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
||||
auto connection = dynamic_cast<NeoChatConnection *>(room->connection());
|
||||
const auto knownServer = roomName.mid(roomName.indexOf(":"_L1) + 1);
|
||||
const auto knownServer = roomName.contains(":"_L1) ? QStringList{roomName.mid(roomName.indexOf(":"_L1) + 1)} : QStringList();
|
||||
if (parts.length() >= 2) {
|
||||
ActionsModel::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});
|
||||
ActionsModel::instance().knockRoom(connection, roomName, parts[1], knownServer);
|
||||
} else {
|
||||
ActionsModel::instance().knockRoom(connection, roomName, QString(), QStringList{knownServer});
|
||||
ActionsModel::instance().knockRoom(connection, roomName, QString(), knownServer);
|
||||
}
|
||||
return QString();
|
||||
},
|
||||
@@ -271,13 +252,7 @@ QList<ActionsModel::Action> actions{
|
||||
Action{
|
||||
u"j"_s,
|
||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
QRegularExpression roomRegex(uR"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"_s);
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
// FIXME: re-add sanity check for roomId/alias
|
||||
if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
|
||||
return QString();
|
||||
|
||||
@@ -100,6 +100,10 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
||||
return plEvent->powerLevelForUser(memberId);
|
||||
}
|
||||
if (role == PowerLevelStringRole) {
|
||||
if (m_currentRoom->roomCreatorHasUltimatePowerLevel() && m_currentRoom->isCreator(memberId)) {
|
||||
return i18nc("@info the person that created this room", "Creator");
|
||||
}
|
||||
|
||||
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
|
||||
// User might not in the room yet, in this case pl can be nullptr.
|
||||
// e.g. When invited but user not accepted or denied the invitation.
|
||||
|
||||
@@ -359,9 +359,14 @@ const RoomEvent *NeoChatRoom::lastEvent(std::function<bool(const RoomEvent *)> f
|
||||
if (auto lastEvent = eventCast<const RoomMessageEvent>(event)) {
|
||||
return lastEvent;
|
||||
}
|
||||
|
||||
if (auto lastEvent = eventCast<const PollStartEvent>(event)) {
|
||||
return lastEvent;
|
||||
}
|
||||
|
||||
if (auto lastEvent = eventCast<const EncryptedEvent>(event)) {
|
||||
return lastEvent;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_cachedEvent != nullptr) {
|
||||
@@ -441,20 +446,19 @@ void NeoChatRoom::onRedaction(const RoomEvent &prevEvent, const RoomEvent & /*af
|
||||
}
|
||||
}
|
||||
|
||||
QDateTime NeoChatRoom::lastActiveTime()
|
||||
QDateTime NeoChatRoom::lastActiveTime() const
|
||||
{
|
||||
if (timelineSize() == 0) {
|
||||
if (m_cachedEvent != nullptr) {
|
||||
return m_cachedEvent->originTimestamp();
|
||||
}
|
||||
return QDateTime();
|
||||
}
|
||||
|
||||
if (auto event = lastEvent()) {
|
||||
// Find the last relevant event:
|
||||
if (const auto event = lastEvent(m_hiddenFilter)) {
|
||||
return event->originTimestamp();
|
||||
}
|
||||
|
||||
// no message found, take last event
|
||||
// If nothing is loaded yet, and there is no cached event:
|
||||
if (timelineSize() == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// No message found, take last event:
|
||||
return messageEvents().rbegin()->get()->originTimestamp();
|
||||
}
|
||||
|
||||
@@ -532,6 +536,9 @@ bool NeoChatRoom::containsUser(const QString &userID) const
|
||||
|
||||
bool NeoChatRoom::canSendEvent(const QString &eventType) const
|
||||
{
|
||||
if (roomCreatorHasUltimatePowerLevel() && isCreator(localMember().id())) {
|
||||
return true;
|
||||
}
|
||||
auto plEvent = currentState().get<RoomPowerLevelsEvent>();
|
||||
if (!plEvent) {
|
||||
return false;
|
||||
@@ -544,6 +551,9 @@ bool NeoChatRoom::canSendEvent(const QString &eventType) const
|
||||
|
||||
bool NeoChatRoom::canSendState(const QString &eventType) const
|
||||
{
|
||||
if (roomCreatorHasUltimatePowerLevel() && isCreator(localMember().id())) {
|
||||
return true;
|
||||
}
|
||||
auto plEvent = currentState().get<RoomPowerLevelsEvent>();
|
||||
if (!plEvent) {
|
||||
return false;
|
||||
@@ -1670,8 +1680,14 @@ void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, con
|
||||
|
||||
NeochatRoomMember *NeoChatRoom::qmlSafeMember(const QString &memberId)
|
||||
{
|
||||
if (memberId.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_memberObjects.contains(memberId)) {
|
||||
return m_memberObjects.emplace(memberId, std::make_unique<NeochatRoomMember>(this, memberId)).first->second.get();
|
||||
auto member = m_memberObjects.emplace(memberId, std::make_unique<NeochatRoomMember>(this, memberId)).first->second.get();
|
||||
QQmlEngine::setObjectOwnership(member, QQmlEngine::CppOwnership);
|
||||
return member;
|
||||
}
|
||||
|
||||
return m_memberObjects[memberId].get();
|
||||
@@ -1731,4 +1747,20 @@ void NeoChatRoom::setHiddenFilter(std::function<bool(const Quotient::RoomEvent *
|
||||
NeoChatRoom::m_hiddenFilter = hiddenFilter;
|
||||
}
|
||||
|
||||
bool NeoChatRoom::roomCreatorHasUltimatePowerLevel() const
|
||||
{
|
||||
bool ok = false;
|
||||
auto version = this->version().toInt(&ok);
|
||||
// This is terrible. For non-numeric room versions, I don't think there's a way of knowing whether they're pre- or post hydra.
|
||||
// We just assume they are. Shouldn't matter for normal users anyway.
|
||||
return !ok || version > 11;
|
||||
}
|
||||
|
||||
bool NeoChatRoom::isCreator(const QString &userId) const
|
||||
{
|
||||
auto createEvent = currentState().get<RoomCreateEvent>();
|
||||
return roomCreatorHasUltimatePowerLevel() && createEvent
|
||||
&& (createEvent->senderId() == userId || createEvent->contentPart<QStringList>(u"additional_creators"_s).contains(userId));
|
||||
}
|
||||
|
||||
#include "moc_neochatroom.cpp"
|
||||
|
||||
@@ -208,7 +208,7 @@ public:
|
||||
bool visible() const;
|
||||
void setVisible(bool visible);
|
||||
|
||||
[[nodiscard]] QDateTime lastActiveTime();
|
||||
[[nodiscard]] QDateTime lastActiveTime() const;
|
||||
|
||||
/**
|
||||
* @brief Get the last interesting event.
|
||||
@@ -589,6 +589,18 @@ public:
|
||||
|
||||
static void setHiddenFilter(std::function<bool(const Quotient::RoomEvent *)> hiddenFilter);
|
||||
|
||||
/**
|
||||
* @brief Whether this room has a room version where the creator is treated as having an ultimate power level
|
||||
*
|
||||
* For unusual room versions, this information might be wrong.
|
||||
*/
|
||||
bool roomCreatorHasUltimatePowerLevel() const;
|
||||
|
||||
/**
|
||||
* @brief Whether this user is considered a creator of this room. Only applies to post-v12 rooms.
|
||||
*/
|
||||
bool isCreator(const QString &userId) const;
|
||||
|
||||
private:
|
||||
bool m_visible = false;
|
||||
|
||||
|
||||
@@ -570,8 +570,9 @@ QVariantMap TextHandler::getAttributes(const QString &tag, const QString &tagStr
|
||||
QList<MessageComponent>
|
||||
TextHandler::textComponents(QString string, Qt::TextFormat inputFormat, const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isEdited)
|
||||
{
|
||||
if (string.isEmpty()) {
|
||||
return {};
|
||||
if (string.trimmed().isEmpty() && event->is<Quotient::RoomMessageEvent>()
|
||||
&& !eventCast<const Quotient::RoomMessageEvent>(event)->has<Quotient::EventContent::FileContentBase>()) {
|
||||
return {MessageComponent{MessageComponentType::Text, i18n("<i>This event does not have any content.</i>"), {}}};
|
||||
}
|
||||
|
||||
// Strip mx-reply if present.
|
||||
@@ -590,7 +591,7 @@ TextHandler::textComponents(QString string, Qt::TextFormat inputFormat, const Ne
|
||||
string = string.trimmed();
|
||||
|
||||
if (event != nullptr && room != nullptr) {
|
||||
if (auto e = eventCast<const Quotient::RoomMessageEvent>(event); e->msgtype() == Quotient::MessageEventType::Emote && components.size() == 1) {
|
||||
if (auto e = eventCast<const Quotient::RoomMessageEvent>(event); e && e->msgtype() == Quotient::MessageEventType::Emote && components.size() == 1) {
|
||||
if (components[0].type == MessageComponentType::Text) {
|
||||
components[0].content = emoteString(room, event) + components[0].content;
|
||||
} else {
|
||||
|
||||
@@ -158,12 +158,7 @@ Item {
|
||||
}
|
||||
root.Message.timeline.interactive = false;
|
||||
if (!root.mediaInfo.isSticker) {
|
||||
// We need to make sure the index is that of the MediaMessageFilterModel.
|
||||
if (root.Message.timeline.model instanceof MessageFilterModel) {
|
||||
RoomManager.maximizeMedia(RoomManager.mediaMessageFilterModel.getRowForSourceItem(root.Message.index));
|
||||
} else {
|
||||
RoomManager.maximizeMedia(root.Message.index);
|
||||
}
|
||||
RoomManager.maximizeMedia(root.eventId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,12 +385,7 @@ Video {
|
||||
onTriggered: {
|
||||
root.Message.timeline.interactive = false;
|
||||
root.pause();
|
||||
// We need to make sure the index is that of the MediaMessageFilterModel.
|
||||
if (root.Message.timeline.model instanceof MessageFilterModel) {
|
||||
RoomManager.maximizeMedia(RoomManager.mediaMessageFilterModel.getRowForSourceItem(root.Message.index));
|
||||
} else {
|
||||
RoomManager.maximizeMedia(root.Message.index);
|
||||
}
|
||||
RoomManager.maximizeMedia(root.eventId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include "chatbarcache.h"
|
||||
#include "contentprovider.h"
|
||||
#include "filetype.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
@@ -422,7 +421,8 @@ bool MessageContentModel::hasComponentType(MessageComponentType::Type type)
|
||||
!= m_components.cend();
|
||||
}
|
||||
|
||||
void MessageContentModel::forEachComponentOfType(MessageComponentType::Type type, std::function<void(const QModelIndex &)> function)
|
||||
void MessageContentModel::forEachComponentOfType(MessageComponentType::Type type,
|
||||
std::function<MessageContentModel::ComponentIt(MessageContentModel::ComponentIt)> function)
|
||||
{
|
||||
auto it = m_components.begin();
|
||||
while ((it = std::find_if(it,
|
||||
@@ -431,12 +431,12 @@ void MessageContentModel::forEachComponentOfType(MessageComponentType::Type type
|
||||
return component.type == type;
|
||||
}))
|
||||
!= m_components.end()) {
|
||||
function(index(it - m_components.begin()));
|
||||
++it;
|
||||
it = function(it);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageContentModel::forEachComponentOfType(QList<MessageComponentType::Type> types, std::function<void(const QModelIndex &)> function)
|
||||
void MessageContentModel::forEachComponentOfType(QList<MessageComponentType::Type> types,
|
||||
std::function<MessageContentModel::ComponentIt(MessageContentModel::ComponentIt)> function)
|
||||
{
|
||||
for (const auto &type : types) {
|
||||
forEachComponentOfType(type, function);
|
||||
@@ -466,6 +466,10 @@ void MessageContentModel::resetModel()
|
||||
m_components += messageContentComponents();
|
||||
endResetModel();
|
||||
|
||||
if (m_room->urlPreviewEnabled()) {
|
||||
forEachComponentOfType({MessageComponentType::Text, MessageComponentType::Quote}, m_linkPreviewFunction);
|
||||
}
|
||||
|
||||
updateReplyModel();
|
||||
updateReactionModel();
|
||||
}
|
||||
@@ -485,6 +489,10 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
||||
m_components += newComponents;
|
||||
endInsertRows();
|
||||
|
||||
if (m_room->urlPreviewEnabled()) {
|
||||
forEachComponentOfType({MessageComponentType::Text, MessageComponentType::Quote}, m_linkPreviewFunction);
|
||||
}
|
||||
|
||||
updateReplyModel();
|
||||
updateReactionModel();
|
||||
}
|
||||
@@ -498,27 +506,13 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
|
||||
QList<MessageComponent> newComponents;
|
||||
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
||||
if (roomMessageEvent && roomMessageEvent->rawMsgtype() == u"m.key.verification.request"_s) {
|
||||
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
|
||||
return newComponents;
|
||||
}
|
||||
|
||||
if (event.first->isRedacted()) {
|
||||
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
return newComponents;
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
} else {
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event.first)));
|
||||
}
|
||||
|
||||
if (m_room->urlPreviewEnabled()) {
|
||||
newComponents = addLinkPreviews(newComponents);
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event.first, m_isReply)));
|
||||
}
|
||||
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
||||
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
|
||||
if (m_threadsEnabled && roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))
|
||||
&& roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
|
||||
@@ -597,22 +591,26 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MessageComponentType::Verification: {
|
||||
return {MessageComponent{MessageComponentType::Verification, QString(), {}}};
|
||||
}
|
||||
case MessageComponentType::Text: {
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
if (body.trimmed().isEmpty()) {
|
||||
return TextHandler().textComponents(i18n("<i>This event does not have any content.</i>"),
|
||||
Qt::TextFormat::RichText,
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
if (const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first)) {
|
||||
return TextHandler().textComponents(EventHandler::rawMessageBody(*roomMessageEvent),
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
} else {
|
||||
return TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
return TextHandler().textComponents(EventHandler::plainBody(m_room, event.first), Qt::TextFormat::PlainText, m_room, event.first, false);
|
||||
}
|
||||
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
||||
return TextHandler().textComponents(EventHandler::rawMessageBody(*roomMessageEvent),
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
}
|
||||
case MessageComponentType::File: {
|
||||
QList<MessageComponent> components;
|
||||
@@ -703,42 +701,20 @@ MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
|
||||
}
|
||||
if (linkPreviewer->loaded()) {
|
||||
return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_L1, link}}};
|
||||
} else {
|
||||
connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() {
|
||||
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
|
||||
for (auto it = m_components.begin(); it != m_components.end(); it++) {
|
||||
if (it->attributes["link"_L1].toUrl() == link) {
|
||||
it->type = MessageComponentType::LinkPreview;
|
||||
Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {ComponentTypeRole});
|
||||
}
|
||||
}
|
||||
connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() {
|
||||
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
|
||||
forEachComponentOfType(MessageComponentType::LinkPreviewLoad, [this, link](ComponentIt it) {
|
||||
if (it->attributes["link"_L1].toUrl() == link) {
|
||||
it->type = MessageComponentType::LinkPreview;
|
||||
Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {ComponentTypeRole});
|
||||
}
|
||||
}
|
||||
});
|
||||
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_L1, link}}};
|
||||
}
|
||||
}
|
||||
|
||||
QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageComponent> inputComponents)
|
||||
{
|
||||
int i = 0;
|
||||
while (i < inputComponents.size()) {
|
||||
const auto component = inputComponents.at(i);
|
||||
if (component.type == MessageComponentType::Text || component.type == MessageComponentType::Quote) {
|
||||
if (LinkPreviewer::hasPreviewableLinks(component.content)) {
|
||||
const auto links = LinkPreviewer::linkPreviews(component.content);
|
||||
for (qsizetype j = 0; j < links.size(); ++j) {
|
||||
const auto linkPreview = linkPreviewComponent(links[j]);
|
||||
if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) {
|
||||
inputComponents.insert(i + j + 1, linkPreview);
|
||||
}
|
||||
};
|
||||
}
|
||||
return it;
|
||||
});
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return inputComponents;
|
||||
});
|
||||
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_L1, link}}};
|
||||
}
|
||||
|
||||
void MessageContentModel::closeLinkPreview(int row)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <Quotient/events/roomevent.h>
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "messagecomponent.h"
|
||||
#include "models/itinerarymodel.h"
|
||||
#include "models/reactionmodel.h"
|
||||
@@ -133,13 +134,32 @@ private:
|
||||
void initializeEvent();
|
||||
void getEvent();
|
||||
|
||||
using ComponentIt = QList<MessageComponent>::iterator;
|
||||
|
||||
QList<MessageComponent> m_components;
|
||||
bool hasComponentType(MessageComponentType::Type type);
|
||||
void forEachComponentOfType(MessageComponentType::Type type, std::function<void(const QModelIndex &)> function);
|
||||
void forEachComponentOfType(QList<MessageComponentType::Type> types, std::function<void(const QModelIndex &)> function);
|
||||
void forEachComponentOfType(MessageComponentType::Type type, std::function<ComponentIt(ComponentIt)> function);
|
||||
void forEachComponentOfType(QList<MessageComponentType::Type> types, std::function<ComponentIt(ComponentIt)> function);
|
||||
|
||||
std::function<void(const QModelIndex &)> m_fileInfoFunction = [this](const QModelIndex &index) {
|
||||
Q_EMIT dataChanged(index, index, {MessageContentModel::FileTransferInfoRole});
|
||||
std::function<ComponentIt(const ComponentIt &)> m_fileInfoFunction = [this](ComponentIt it) {
|
||||
Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {MessageContentModel::FileTransferInfoRole});
|
||||
return ++it;
|
||||
};
|
||||
std::function<ComponentIt(const ComponentIt &)> m_linkPreviewFunction = [this](ComponentIt it) {
|
||||
bool previewAdded = false;
|
||||
if (LinkPreviewer::hasPreviewableLinks(it->content)) {
|
||||
const auto links = LinkPreviewer::linkPreviews(it->content);
|
||||
for (qsizetype j = 0; j < links.size(); ++j) {
|
||||
const auto linkPreview = linkPreviewComponent(links[j]);
|
||||
if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) {
|
||||
beginInsertRows({}, std::distance(m_components.begin(), it) + j + 1, std::distance(m_components.begin(), it) + j + 1);
|
||||
it = m_components.insert(it + j + 1, linkPreview);
|
||||
previewAdded = true;
|
||||
endInsertRows();
|
||||
}
|
||||
};
|
||||
}
|
||||
return previewAdded ? it : ++it;
|
||||
};
|
||||
|
||||
void resetModel();
|
||||
@@ -154,7 +174,6 @@ private:
|
||||
|
||||
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
|
||||
MessageComponent linkPreviewComponent(const QUrl &link);
|
||||
QList<MessageComponent> addLinkPreviews(QList<MessageComponent> inputComponents);
|
||||
|
||||
QList<QUrl> m_removedLinkPreviews;
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ QQC2.ScrollView {
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: leaveButton
|
||||
icon.name: "arrow-left-symbolic"
|
||||
text: root.room.isSpace ? i18nc("@action:button", "Leave this space") : i18nc("@action:button", "Leave this room")
|
||||
text: root.room.isSpace ? i18nc("@action:button", "Leave this space…") : i18nc("@action:button", "Leave this room…")
|
||||
activeFocusOnTab: true
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -58,7 +58,7 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
icon.name: "notifications"
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18n("Follow Global Setting")
|
||||
text: i18nc("@action:inmenu Notification 'Default Settings'", "Default Settings")
|
||||
icon.name: "globe"
|
||||
checkable: true
|
||||
autoExclusive: true
|
||||
@@ -152,7 +152,7 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
}
|
||||
|
||||
QQC2.Action {
|
||||
text: i18n("Leave Room")
|
||||
text: i18n("Leave Room…")
|
||||
icon.name: "go-previous"
|
||||
onTriggered: {
|
||||
Qt.createComponent('org.kde.neochat', 'ConfirmLeaveDialog').createObject(root.QQC2.ApplicationWindow.window, {
|
||||
|
||||
@@ -70,8 +70,10 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
}
|
||||
|
||||
QQC2.Action {
|
||||
text: i18nc("'Space' is a matrix space", "Leave Space")
|
||||
text: i18nc("'Space' is a matrix space", "Leave Space…")
|
||||
icon.name: "go-previous"
|
||||
onTriggered: root.room.forget()
|
||||
onTriggered: Qt.createComponent('org.kde.neochat', 'ConfirmLeaveDialog').createObject(root.QQC2.ApplicationWindow.window, {
|
||||
room: root.room
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ FormCard.FormCardPage {
|
||||
FormCard.FormCard {
|
||||
FormCard.FormButtonDelegate {
|
||||
id: deactivateAccountButton
|
||||
text: i18n("Deactivate Account")
|
||||
text: i18nc("@action:button", "Deactivate Account…")
|
||||
icon.name: "trash-empty-symbolic"
|
||||
onClicked: {
|
||||
const component = Qt.createComponent('org.kde.neochat', 'ConfirmDeactivateAccountDialog');
|
||||
|
||||
@@ -85,7 +85,7 @@ FormCard.FormCardPage {
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
text: i18n("Logout")
|
||||
text: i18n("Logout…")
|
||||
icon.name: "im-kick-user"
|
||||
onClicked: confirmLogoutDialogComponent.createObject(root.QQC2.Overlay.overlay).open()
|
||||
}
|
||||
|
||||
@@ -45,6 +45,26 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
FormCard.AbstractFormDelegate {
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
Kirigami.Icon {
|
||||
source: "data-information"
|
||||
width: Kirigami.Units.iconSizes.sizeForLabels
|
||||
height: Kirigami.Units.iconSizes.sizeForLabels
|
||||
}
|
||||
QQC2.Label {
|
||||
text: i18nc("@info", "These are the default notification settings for all rooms. You can customize notifications per-room in the room list or room settings.")
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title:group", "Room Notifications")
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ FormCard.FormCardPage {
|
||||
FormCard.FormCard {
|
||||
FormCard.FormButtonDelegate {
|
||||
icon.name: "kt-restore-defaults-symbolic"
|
||||
text: i18nc("@action:button", "Reset all configuration values to their default")
|
||||
text: i18nc("@action:button", "Reset all configuration values to their default…")
|
||||
onClicked: resetDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ FormCard.FormCardPage {
|
||||
|
||||
FormCard.FormCard {
|
||||
FormCard.FormRadioDelegate {
|
||||
text: i18n("Follow global setting")
|
||||
text: i18nc("As in the default notification setting", "Default Settings")
|
||||
checked: room.pushNotificationState === PushNotificationState.Default
|
||||
enabled: room.pushNotificationState !== PushNotificationState.Unknown
|
||||
onToggled: {
|
||||
|
||||
@@ -25,13 +25,34 @@ FormCard.FormCardPage {
|
||||
title: i18nc("@option:check", "Encryption")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormSwitchDelegate {
|
||||
FormCard.AbstractFormDelegate {
|
||||
visible: room.usesEncryption
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
Kirigami.Icon {
|
||||
source: "lock"
|
||||
width: Kirigami.Units.iconSizes.sizeForLabels
|
||||
height: Kirigami.Units.iconSizes.sizeForLabels
|
||||
}
|
||||
QQC2.Label {
|
||||
text: i18nc("@info", "This room uses encryption.")
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormButtonDelegate {
|
||||
id: enableEncryptionSwitch
|
||||
text: i18n("Enable encryption")
|
||||
description: i18nc("option:check", "Once enabled, encryption cannot be disabled.")
|
||||
|
||||
icon.name: "lock-symbolic"
|
||||
text: i18nc("@action:button Enable encryption in this room", "Enable Encryption…")
|
||||
description: i18nc("@info:description", "Once enabled, encryption cannot be disabled.")
|
||||
enabled: room.canEncryptRoom
|
||||
checked: room.usesEncryption
|
||||
onToggled: if (checked) {
|
||||
visible: !room.usesEncryption
|
||||
|
||||
onClicked: {
|
||||
let dialog = confirmEncryptionDialog.createObject(QQC2.Overlay.overlay, {
|
||||
room: room
|
||||
});
|
||||
|
||||
@@ -95,9 +95,11 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button", "Leave this space")
|
||||
text: i18nc("@action:button", "Leave this space…")
|
||||
icon.name: "go-previous"
|
||||
onClicked: root.room.forget()
|
||||
onClicked: Qt.createComponent('org.kde.neochat', 'ConfirmLeaveDialog').createObject(root.QQC2.ApplicationWindow.window, {
|
||||
room: root.room
|
||||
}).open();
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -123,7 +123,7 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
|
||||
|
||||
component ReportMessageAction: Kirigami.Action {
|
||||
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
|
||||
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report…")
|
||||
icon.name: "dialog-warning-symbolic"
|
||||
visible: !author.isLocalMember
|
||||
onTriggered: {
|
||||
|
||||
@@ -159,7 +159,7 @@ QQC2.ScrollView {
|
||||
|
||||
function onReadMarkerAdded() {
|
||||
if (root.markReadCondition == LibNeoChat.TimelineMarkReadCondition.EntryVisible && messageListView.allUnreadVisible()) {
|
||||
root.room.markAllMessagesAsRead();
|
||||
_private.room.markAllMessagesAsRead();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,9 +85,14 @@ QHash<int, QByteArray> MediaMessageFilterModel::roleNames() const
|
||||
return roles;
|
||||
}
|
||||
|
||||
int MediaMessageFilterModel::getRowForSourceItem(int sourceRow) const
|
||||
int MediaMessageFilterModel::getRowForEventId(const QString &eventId) const
|
||||
{
|
||||
return mapFromSource(sourceModel()->index(sourceRow, 0)).row();
|
||||
for (auto i = 0; i < rowCount(); i++) {
|
||||
if (data(index(i, 0), MessageModel::EventIdRole).toString() == eventId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#include "moc_mediamessagefiltermodel.cpp"
|
||||
|
||||
@@ -63,5 +63,5 @@ public:
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE int getRowForSourceItem(int sourceRow) const;
|
||||
int getRowForEventId(const QString &eventId) const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user