Compare commits

..

1 Commits

Author SHA1 Message Date
Tomaz Canabrava
0083199493 Fix crash on Qt6/KF6 2023-10-02 17:39:05 +00:00
125 changed files with 7546 additions and 12532 deletions

View File

@@ -19,7 +19,6 @@
"--talk-name=org.freedesktop.Notifications",
"--talk-name=org.kde.kwalletd5",
"--talk-name=org.kde.StatusNotifierWatcher",
"--talk-name=org.freedesktop.secrets",
"--own-name=org.kde.StatusNotifierItem-2-2"
],
"modules": [

View File

@@ -3,7 +3,7 @@
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml

View File

@@ -41,7 +41,3 @@ License: CC0-1.0
Files: .flatpak-manifest.json
Copyright: 2020-2022 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: autotests/data/*
Copyright: none
License: CC0-1.0

View File

@@ -101,8 +101,7 @@ set_package_properties(QuotientQt6 PROPERTIES
PURPOSE "Talk with matrix server"
)
# The android part is just for CI. We do NOT support any builds without E2EE
if (NOT TARGET Olm::Olm AND NOT ANDROID)
if (NOT TARGET Olm::Olm)
message(FATAL_ERROR "NeoChat requires Quotient with the E2EE feature enabled")
endif()
@@ -118,7 +117,7 @@ set_package_properties(cmark PROPERTIES
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
ecm_find_qmlmodule(org.kde.kitemmodels 1.0)
ecm_find_qmlmodule(org.kde.quickcharts 1.0)
ecm_find_qmlmodule(QtLocation)
ecm_find_qmlmodule(QtLocation ${QTLOCATION_MODULE_QML_VERSION})
find_package(KQuickImageEditor COMPONENTS)
set_package_properties(KQuickImageEditor PROPERTIES

View File

@@ -3,8 +3,6 @@
enable_testing()
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
ecm_add_test(
neochatroomtest.cpp
LINK_LIBRARIES neochat Qt::Test
@@ -34,9 +32,3 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test
TEST_NAME eventhandlertest
)
ecm_add_test(
chatbarcachetest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME chatbarcachetest
)

View File

@@ -1,157 +0,0 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QJsonDocument>
#include <QJsonObject>
#include <QObject>
#include <QTest>
#include <Quotient/syncdata.h>
#include <qtestcase.h>
#include "chatbarcache.h"
#include "neochatroom.h"
using namespace Quotient;
class TestRoom : public NeoChatRoom
{
public:
using NeoChatRoom::NeoChatRoom;
void update(SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
};
class ChatBarCacheTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void empty();
void noRoom();
void badParent();
void reply();
void edit();
void attachment();
};
void ChatBarCacheTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testMinSyncFile;
testMinSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-min-sync.json"));
testMinSyncFile.open(QIODevice::ReadOnly);
const auto testMinSyncJson = QJsonDocument::fromJson(testMinSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testMinSyncJson.object());
room->update(std::move(roomData));
}
void ChatBarCacheTest::empty()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
QCOMPARE(chatBarCache->text(), QString());
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
void ChatBarCacheTest::noRoom()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache());
chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
// These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString());
}
void ChatBarCacheTest::badParent()
{
QScopedPointer<QObject> badParent(new QObject());
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(badParent.get()));
chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
// These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString());
}
void ChatBarCacheTest::reply()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), true);
QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
void ChatBarCacheTest::edit()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), true);
QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
void ChatBarCacheTest::attachment()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
}
QTEST_MAIN(ChatBarCacheTest)
#include "chatbarcachetest.moc"

View File

@@ -1,381 +0,0 @@
{
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
},
{
"content": {
"$153456789:example.org": {
"m.read": {
"@alice:matrix.org": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@bob:example.com": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tim:example.com": {
"ts": 1436451550454
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@jeff:example.com": {
"ts": 1436451550455
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tina:example.com": {
"ts": 1436451550456
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@sally:example.com": {
"ts": 1436451550457
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@fred:example.com": {
"ts": 1436451550458
}
}
}
},
"type": "m.receipt"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example\ntext message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example<br>text message</b>",
"msgtype": "m.text"
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"avatar_url": "mxc://kde.org/123456",
"displayname": "after",
"membership": "join"
},
"origin_server_ts": 1690651134736,
"sender": "@example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"replaces_state": "$1234567890:example.org",
"prev_content": {
"avatar_url": "mxc://kde.org/12345",
"displayname": "before",
"membership": "join"
},
"prev_sender": "@example:example.orgg",
"age": 1234
},
"event_id": "$143273583553PhrSn:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"content": {
"body": "This is a highlight @bob:kde.org and this is a link https://kde.org",
"format": "org.matrix.custom.html",
"msgtype": "m.text"
},
"event_id": "$1532735824654:example.org",
"origin_server_ts": 1532735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$153456789:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1690322545182,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:matrix.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$163456789:example.org",
"age": 390159120
},
{
"age": 4926305285,
"content": {
"body": "video caption",
"filename": "video.mp4",
"info": {
"duration": 10,
"h": 1080,
"mimetype": "video/mp4",
"size": 62650636,
"w": 1920,
"thumbnail_info": {
"h": 450,
"mimetype": "image/jpeg",
"size": 382249,
"w": 800
},
"thumbnail_url": "mxc://kde.org/2234567"
},
"msgtype": "m.video",
"url": "mxc://kde.org/1234567"
},
"event_id": "$263456789:example.org",
"origin_server_ts": 1685793783330,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 4926305285
},
"user_id": "@example:example.org"
},
{
"content": {
"body": "> <@example:example.org> This is an example\ntext message\n\nreply",
"format": "org.matrix.custom.html",
"formatted_body": "<mx-reply><blockquote><a href=\"https://matrix.to/#/!jEsUZKDJdhlrceRyVU:example.org/$153456789:example.org?via=kde.org&via=matrix.org\">In reply to</a> <a href=\"https://matrix.to/#/@example:example.org\">@example:example.org</a><br><b>This is an example<br>text message</b></blockquote></mx-reply>reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$153456789:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1690725965572,
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$154456789:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"content": {
"body": "> <@example:example.org> video caption\n\nreply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$263456789:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1690725965573,
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$154456799:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": "$1544567999:example.org",
"origin_server_ts": 1690821582876,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
},
{
"content": {
"body": "Thread root",
"format": "org.matrix.custom.html",
"msgtype": "m.text"
},
"event_id": "$threadroot:example.org",
"origin_server_ts": 1690821582879,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "Thread message 1",
"msgtype": "m.text",
"m.relates_to": {
"rel_type": "m.thread",
"event_id": "$threadroot:example.org",
"m.in_reply_to": {
"event_id": "$threadroot:example.org"
},
"is_falling_back": true
}
},
"event_id": "$threadmessage1:example.org",
"origin_server_ts": 1690821582890,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1238
}
},
{
"content": {
"body": "Thread message 2",
"msgtype": "m.text",
"m.relates_to": {
"rel_type": "m.thread",
"event_id": "$threadroot:example.org",
"m.in_reply_to": {
"event_id": "$threadmessage1:example.org"
},
"is_falling_back": true
}
},
"event_id": "$threadmessage2:example.org",
"origin_server_ts": 1690821582890,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1238
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -1,87 +0,0 @@
{
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example\ntext message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example<br>text message</b>",
"msgtype": "m.text"
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -64,7 +64,6 @@ private Q_SLOTS:
void replyAuthor();
void replyBody();
void replyMediaInfo();
void thread();
void location();
void readMarkers();
};
@@ -74,11 +73,329 @@ void EventHandlerTest::initTestCase()
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testEventHandlerSyncFile;
testEventHandlerSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-eventhandler-sync.json"));
testEventHandlerSyncFile.open(QIODevice::ReadOnly);
const auto testEventHandlerSyncJson = QJsonDocument::fromJson(testEventHandlerSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testEventHandlerSyncJson.object());
const auto json = QJsonDocument::fromJson(R"EVENT({
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
},
{
"content": {
"$153456789:example.org": {
"m.read": {
"@alice:matrix.org": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@bob:example.com": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tim:example.com": {
"ts": 1436451550454
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@jeff:example.com": {
"ts": 1436451550455
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tina:example.com": {
"ts": 1436451550456
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@sally:example.com": {
"ts": 1436451550457
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@fred:example.com": {
"ts": 1436451550458
}
}
}
},
"type": "m.receipt"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example\ntext message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example<br>text message</b>",
"msgtype": "m.text"
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"avatar_url": "mxc://kde.org/123456",
"displayname": "after",
"membership": "join"
},
"origin_server_ts": 1690651134736,
"sender": "@example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"replaces_state": "$1234567890:example.org",
"prev_content": {
"avatar_url": "mxc://kde.org/12345",
"displayname": "before",
"membership": "join"
},
"prev_sender": "@example:example.orgg",
"age": 1234
},
"event_id": "$143273583553PhrSn:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"content": {
"body": "This is a highlight @bob:kde.org and this is a link https://kde.org",
"format": "org.matrix.custom.html",
"msgtype": "m.text"
},
"event_id": "$1532735824654:example.org",
"origin_server_ts": 1532735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$153456789:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1690322545182,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:matrix.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$163456789:example.org",
"age": 390159120
},
{
"age": 4926305285,
"content": {
"body": "video caption",
"filename": "video.mp4",
"info": {
"duration": 10,
"h": 1080,
"mimetype": "video/mp4",
"size": 62650636,
"w": 1920,
"thumbnail_info": {
"h": 450,
"mimetype": "image/jpeg",
"size": 382249,
"w": 800
},
"thumbnail_url": "mxc://kde.org/2234567"
},
"msgtype": "m.video",
"url": "mxc://kde.org/1234567"
},
"event_id": "$263456789:example.org",
"origin_server_ts": 1685793783330,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 4926305285
},
"user_id": "@example:example.org"
},
{
"content": {
"body": "> <@example:example.org> This is an example\ntext message\n\nreply",
"format": "org.matrix.custom.html",
"formatted_body": "<mx-reply><blockquote><a href=\"https://matrix.to/#/!jEsUZKDJdhlrceRyVU:example.org/$153456789:example.org?via=kde.org&via=matrix.org\">In reply to</a> <a href=\"https://matrix.to/#/@example:example.org\">@example:example.org</a><br><b>This is an example<br>text message</b></blockquote></mx-reply>reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$153456789:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1690725965572,
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$154456789:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"content": {
"body": "> <@example:example.org> video caption\n\nreply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$263456789:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1690725965573,
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$154456799:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": "$1544567999:example.org",
"origin_server_ts": 1690821582876,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
})EVENT");
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
room->update(std::move(roomData));
eventHandler.setRoom(room);
@@ -369,29 +686,6 @@ void EventHandlerTest::replyMediaInfo()
QCOMPARE(thumbnailInfo["height"_ls], 450);
}
void EventHandlerTest::thread()
{
auto event = room->messageEvents().at(0).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.isThreaded(), false);
QCOMPARE(eventHandler.threadRoot(), QString());
event = room->messageEvents().at(9).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.isThreaded(), true);
QCOMPARE(eventHandler.threadRoot(), QStringLiteral("$threadroot:example.org"));
QCOMPARE(eventHandler.getReplyId(), QStringLiteral("$threadroot:example.org"));
event = room->messageEvents().at(10).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.isThreaded(), true);
QCOMPARE(eventHandler.threadRoot(), QStringLiteral("$threadroot:example.org"));
QCOMPARE(eventHandler.getReplyId(), QStringLiteral("$threadmessage1:example.org"));
}
void EventHandlerTest::location()
{
auto event = room->messageEvents().at(7).get();

View File

@@ -283,7 +283,7 @@ to provide a convergent experience across multiple platforms.</p>
<screenshot type="default">
<image>https://cdn.kde.org/screenshots/neochat/application.png</image>
</screenshot>
<screenshot environment="windows">
<screenshot x-kde-os="windows">
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Timeline.png</image>
<caption>Main view with room list, chat, and room information</caption>
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
@@ -310,7 +310,7 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
</screenshot>
<screenshot environment="windows">
<screenshot x-kde-os="windows">
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</image>
<caption>Login screen</caption>
<caption xml:lang="ar">شاشة الدخول</caption>
@@ -342,7 +342,6 @@ to provide a convergent experience across multiple platforms.</p>
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="23.08.2" date="2023-10-12"/>
<release version="23.08.0" date="2023-08-24">
<url>https://kde.org/announcements/gear/23.08.0/#neochathttpsappskdeorgneochat</url>
<description>

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@@ -135,8 +135,6 @@ add_library(neochat STATIC
enums/delegatetype.h
roomlastmessageprovider.cpp
roomlastmessageprovider.h
chatbarcache.cpp
chatbarcache.h
)
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
@@ -154,7 +152,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/RoomPage.qml
qml/RoomWindow.qml
qml/JoinRoomPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/StartChatPage.qml
@@ -284,7 +281,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/SpaceHomePage.qml
qml/SpaceHierarchyDelegate.qml
qml/RemoveChildDialog.qml
qml/SelectParentDialog.qml
RESOURCES
qml/confetti.png
qml/glowdot.png
@@ -432,7 +428,6 @@ if(ANDROID)
"preferences-desktop-notification"
"computer-symbolic"
"gps"
"system-users-symbolic"
)
else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets)

View File

@@ -35,35 +35,48 @@ void ActionsHandler::setRoom(NeoChatRoom *room)
}
m_room = room;
Q_EMIT roomChanged();
}
void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
void ActionsHandler::handleNewMessage()
{
if (!chatBarCache) {
return;
}
checkEffects(chatBarCache->text());
if (!chatBarCache->attachmentPath().isEmpty()) {
QUrl url(chatBarCache->attachmentPath());
checkEffects(m_room->chatBoxText());
if (!m_room->chatBoxAttachmentPath().isEmpty()) {
QUrl url(m_room->chatBoxAttachmentPath());
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
m_room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
chatBarCache->setAttachmentPath({});
chatBarCache->setText({});
m_room->uploadFile(QUrl(path), m_room->chatBoxText().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : m_room->chatBoxText());
m_room->setChatBoxAttachmentPath({});
m_room->setChatBoxText({});
return;
}
QString handledText = chatBarCache->text();
handledText = handleMentions(handledText, chatBarCache->mentions());
handleMessage(m_room->mainCache()->text(), handledText, chatBarCache);
QString handledText = m_room->chatBoxText();
handledText = handleMentions(handledText);
handleMessage(m_room->chatBoxText(), handledText);
}
QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *mentions)
void ActionsHandler::handleEdit()
{
checkEffects(m_room->editText());
QString handledText = m_room->editText();
handledText = handleMentions(handledText, true);
handleMessage(m_room->editText(), handledText, true);
}
QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
{
if (!m_room) {
return QString();
}
QVector<Mention> *mentions;
if (isEdit) {
mentions = m_room->editMentions();
} else {
mentions = m_room->mentions();
}
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
return a.cursor.anchor() > b.cursor.anchor();
});
@@ -81,7 +94,7 @@ QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *ment
return handledText;
}
void ActionsHandler::handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache)
void ActionsHandler::handleMessage(const QString &text, QString handledText, const bool &isEdit)
{
if (NeoChatConfig::allowQuickEdit()) {
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
@@ -121,7 +134,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
for (const auto &action : ActionsModel::instance().allActions()) {
if (handledText.indexOf(action.prefix) == 1
&& (handledText.indexOf(" "_ls) == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room, chatBarCache);
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room);
if (action.messageType.has_value()) {
messageType = *action.messageType;
}
@@ -148,7 +161,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
return;
}
m_room->postMessage(text, handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
m_room->postMessage(text, handledText, messageType, m_room->chatBoxReplyId(), isEdit ? m_room->chatBoxEditId() : QString());
}
void ActionsHandler::checkEffects(const QString &text)

View File

@@ -8,7 +8,6 @@
#include <Quotient/events/roommessageevent.h>
#include "chatbarcache.h"
#include "neochatroom.h"
class NeoChatRoom;
@@ -35,30 +34,38 @@ class ActionsHandler : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
explicit ActionsHandler(QObject *parent = nullptr);
/**
* @brief The room that messages will be sent to.
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
public:
explicit ActionsHandler(QObject *parent = nullptr);
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
Q_SIGNALS:
void roomChanged();
void showEffect(const QString &effect);
public Q_SLOTS:
/**
* @brief Pre-process text and send message event.
* @brief Pre-process text and send message.
*/
void handleMessageEvent(ChatBarCache *chatBarCache);
void handleNewMessage();
/**
* @brief Pre-process text and send edit.
*/
void handleEdit();
private:
NeoChatRoom *m_room = nullptr;
void checkEffects(const QString &text);
QString handleMentions(QString handledText, QList<Mention> *mentions);
void handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache);
QString handleMentions(QString handledText, const bool &isEdit = false);
void handleMessage(const QString &text, QString handledText, const bool &isEdit = false);
};

View File

@@ -1,175 +0,0 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "chatbarcache.h"
#include "eventhandler.h"
#include "neochatroom.h"
ChatBarCache::ChatBarCache(QObject *parent)
: QObject(parent)
{
}
QString ChatBarCache::text() const
{
return m_text;
}
void ChatBarCache::setText(const QString &text)
{
if (text == m_text) {
return;
}
m_text = text;
Q_EMIT textChanged();
}
bool ChatBarCache::isReplying() const
{
return m_relationType == Reply && !m_relationId.isEmpty();
}
QString ChatBarCache::replyId() const
{
if (m_relationType != Reply) {
return {};
}
return m_relationId;
}
void ChatBarCache::setReplyId(const QString &replyId)
{
if (m_relationType == Reply && m_relationId == replyId) {
return;
}
m_relationId = replyId;
if (m_relationId.isEmpty()) {
m_relationType = None;
} else {
m_relationType = Reply;
}
m_attachmentPath = QString();
Q_EMIT relationIdChanged();
Q_EMIT attachmentPathChanged();
}
bool ChatBarCache::isEditing() const
{
return m_relationType == Edit && !m_relationId.isEmpty();
}
QString ChatBarCache::editId() const
{
if (m_relationType != Edit) {
return {};
}
return m_relationId;
}
void ChatBarCache::setEditId(const QString &editId)
{
if (m_relationType == Edit && m_relationId == editId) {
return;
}
m_relationId = editId;
if (m_relationId.isEmpty()) {
m_relationType = None;
} else {
m_relationType = Edit;
}
m_attachmentPath = QString();
Q_EMIT relationIdChanged();
Q_EMIT attachmentPathChanged();
}
QVariantMap ChatBarCache::relationUser() const
{
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
if (m_relationId.isEmpty()) {
return room->getUser(nullptr);
}
return room->getUser(room->user((*room->findInTimeline(m_relationId))->senderId()));
}
QString ChatBarCache::relationMessage() const
{
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
if (m_relationId.isEmpty()) {
return {};
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
EventHandler eventhandler;
eventhandler.setRoom(room);
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
eventhandler.setEvent(&**event);
return eventhandler.getPlainBody();
}
return {};
}
bool ChatBarCache::isThreaded() const
{
return !m_threadId.isEmpty();
}
QString ChatBarCache::threadId() const
{
return m_threadId;
}
void ChatBarCache::setThreadId(const QString &threadId)
{
if (m_threadId == threadId) {
return;
}
m_threadId = threadId;
Q_EMIT threadIdChanged();
}
QString ChatBarCache::attachmentPath() const
{
return m_attachmentPath;
}
void ChatBarCache::setAttachmentPath(const QString &attachmentPath)
{
if (attachmentPath == m_attachmentPath) {
return;
}
m_attachmentPath = attachmentPath;
m_relationType = None;
m_relationId = QString();
Q_EMIT attachmentPathChanged();
Q_EMIT relationIdChanged();
}
QList<Mention> *ChatBarCache::mentions()
{
return &m_mentions;
}
QString ChatBarCache::savedText() const
{
return m_savedText;
}
void ChatBarCache::setSavedText(const QString &savedText)
{
m_savedText = savedText;
}

View File

@@ -1,201 +0,0 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QTextCursor>
/**
* @brief Defines a user mention in the current chat or edit text.
*/
struct Mention {
QTextCursor cursor; /**< Contains the mention's text and position in the text. */
QString text; /**< The inserted text of the mention. */
int start = 0; /**< Start position of the mention. */
int position = 0; /**< End position of the mention. */
QString id; /**< The id the mention (used to create link when sending the message). */
};
/**
* @class ChatBarCache
*
* A class to cache data from a chat bar.
*
* A chat bar can be anything that allows users to compose or edit message, it doesn't
* necessarily have to use the ChatBar component, e.g. MessageEditComponent.
*
* This object is intended to allow the current contents of a chat bar to be cached
* between different rooms, i.e. there is an expectation that each NeoChatRoom could
* have a separate cache for each chat bar.
*
* @note The NeoChatRoom which this component is created in is expected to be set
* as it's parent. This is necessary for certain functions which need to get
* relevant room information.
*
* @sa ChatBar, MessageEditComponent, NeoChatRoom
*/
class ChatBarCache : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
/**
* @brief The text in the chat bar.
*
* Due to problems with QTextDocument, unlike the other properties here,
* text is *not* used to store the text when switching rooms.
*/
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
/**
* @brief Whether the chat bar is currently replying to a message.
*/
Q_PROPERTY(bool isReplying READ isReplying NOTIFY relationIdChanged)
/**
* @brief The Matrix message ID of an event being replied to, if any.
*
* Will return empty if the RelationType is currently set to None or Edit.
*
* @note Replying, editing and attachments are exclusive so setting this will
* clear an edit or attachment.
*
* @sa RelationType
*/
Q_PROPERTY(QString replyId READ replyId WRITE setReplyId NOTIFY relationIdChanged)
/**
* @brief Whether the chat bar is currently editing a message.
*/
Q_PROPERTY(bool isEditing READ isEditing NOTIFY relationIdChanged)
/**
* @brief The Matrix message ID of an event being edited, if any.
*
* Will return empty if the RelationType is currently set to None or Reply.
*
* @note Replying, editing and attachments are exclusive so setting this will
* clear an reply or attachment.
*
* @sa RelationType
*/
Q_PROPERTY(QString editId READ editId WRITE setEditId NOTIFY relationIdChanged)
/**
* @brief Get the user for the message being replied to.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
*
* Returns an empty user if not replying to a message.
*
* The user QVariantMap has the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User
*/
Q_PROPERTY(QVariantMap relationUser READ relationUser NOTIFY relationIdChanged)
/**
* @brief The content of the related message.
*
* Will be QString() if no related message.
*/
Q_PROPERTY(QString relationMessage READ relationMessage NOTIFY relationIdChanged)
/**
* @brief Whether the chat bar is replying in a thread.
*/
Q_PROPERTY(bool isThreaded READ isThreaded NOTIFY threadIdChanged)
/**
* @brief The Matrix message ID of thread root event, if any.
*/
Q_PROPERTY(QString threadId READ threadId WRITE setThreadId NOTIFY threadIdChanged)
/**
* @brief The local path for a file to send, if any.
*
* @note Replying, editing and attachments are exclusive so setting this will
* clear an edit or reply.
*/
Q_PROPERTY(QString attachmentPath READ attachmentPath WRITE setAttachmentPath NOTIFY attachmentPathChanged)
public:
/**
* @brief Describes the type of relation which relationId can refer to.
*
* A chat bar can only be relating to a single message at a time making these
* exclusive.
*/
enum RelationType {
Reply, /**< The current relation is a message being replied to. */
Edit, /**< The current relation is a message being edited. */
None, /**< There is currently no relation event */
};
Q_ENUM(RelationType)
explicit ChatBarCache(QObject *parent = nullptr);
QString text() const;
void setText(const QString &text);
bool isReplying() const;
QString replyId() const;
void setReplyId(const QString &replyId);
bool isEditing() const;
QString editId() const;
void setEditId(const QString &editId);
QVariantMap relationUser() const;
QString relationMessage() const;
bool isThreaded() const;
QString threadId() const;
void setThreadId(const QString &threadId);
QString attachmentPath() const;
void setAttachmentPath(const QString &attachmentPath);
/**
* @brief Retrieve the mentions for the current chat bar text.
*/
QList<Mention> *mentions();
/**
* @brief Get the saved chat bar text.
*/
QString savedText() const;
/**
* @brief Save the chat bar text.
*/
void setSavedText(const QString &savedText);
Q_SIGNALS:
void textChanged();
void relationIdChanged();
void threadIdChanged();
void attachmentPathChanged();
private:
QString m_text = QString();
QString m_relationId = QString();
RelationType m_relationType = RelationType::None;
QString m_threadId = QString();
QString m_attachmentPath = QString();
QList<Mention> m_mentions;
QString m_savedText;
};

View File

@@ -57,12 +57,11 @@ public:
setFormat(error.first, error.second.size(), errorFormat);
}
}
auto handler = dynamic_cast<ChatDocumentHandler *>(parent());
auto room = handler->room();
auto room = dynamic_cast<ChatDocumentHandler *>(parent())->room();
if (!room) {
return;
}
auto mentions = handler->chatBarCache()->mentions();
auto mentions = room->mentions();
mentions->erase(std::remove_if(mentions->begin(),
mentions->end(),
[this](auto &mention) {
@@ -104,10 +103,15 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
m_completionModel->setRoom(m_room);
static QPointer<NeoChatRoom> previousRoom = nullptr;
if (previousRoom) {
disconnect(m_chatBarCache, &ChatBarCache::textChanged, this, nullptr);
disconnect(previousRoom, &NeoChatRoom::chatBoxTextChanged, this, nullptr);
disconnect(previousRoom, &NeoChatRoom::editTextChanged, this, nullptr);
}
previousRoom = m_room;
connect(m_chatBarCache, &ChatBarCache::textChanged, this, [this]() {
connect(m_room, &NeoChatRoom::chatBoxTextChanged, this, [this]() {
int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
connect(m_room, &NeoChatRoom::editTextChanged, this, [this]() {
int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
@@ -211,20 +215,6 @@ void ChatDocumentHandler::setRoom(NeoChatRoom *room)
Q_EMIT roomChanged();
}
ChatBarCache *ChatDocumentHandler::chatBarCache() const
{
return m_chatBarCache;
}
void ChatDocumentHandler::setChatBarCache(ChatBarCache *chatBarCache)
{
if (m_chatBarCache == chatBarCache) {
return;
}
m_chatBarCache = chatBarCache;
Q_EMIT chatBarCacheChanged();
}
void ChatDocumentHandler::complete(int index)
{
if (m_completionModel->autoCompletionType() == CompletionModel::User) {
@@ -313,7 +303,11 @@ QString ChatDocumentHandler::getText() const
if (!m_room) {
return QString();
}
return m_chatBarCache->text();
if (m_isEdit) {
return m_room->editText();
} else {
return m_room->chatBoxText();
}
}
void ChatDocumentHandler::pushMention(const Mention mention) const
@@ -321,7 +315,11 @@ void ChatDocumentHandler::pushMention(const Mention mention) const
if (!m_room) {
return;
}
m_chatBarCache->mentions()->push_back(mention);
if (m_isEdit) {
m_room->editMentions()->push_back(mention);
} else {
m_room->mentions()->push_back(mention);
}
}
QColor ChatDocumentHandler::mentionColor() const

View File

@@ -8,7 +8,6 @@
#include <QQuickTextDocument>
#include <QTextCursor>
#include "chatbarcache.h"
#include "models/completionmodel.h"
#include "neochatroom.h"
@@ -103,11 +102,6 @@ class ChatDocumentHandler : public QObject
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
/**
* @brief The cache for the chat bar the text document is being handled for.
*/
Q_PROPERTY(ChatBarCache *chatBarCache READ chatBarCache WRITE setChatBarCache NOTIFY chatBarCacheChanged)
/**
* @brief The color to highlight user mentions.
*/
@@ -139,9 +133,6 @@ public:
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
[[nodiscard]] ChatBarCache *chatBarCache() const;
void setChatBarCache(ChatBarCache *chatBarCache);
Q_INVOKABLE void complete(int index);
void updateCompletions();
@@ -158,7 +149,6 @@ Q_SIGNALS:
void documentChanged();
void cursorPositionChanged();
void roomChanged();
void chatBarCacheChanged();
void completionModelChanged();
void selectionStartChanged();
void selectionEndChanged();
@@ -173,7 +163,6 @@ private:
QPointer<QQuickTextDocument> m_document;
QPointer<NeoChatRoom> m_room;
QPointer<ChatBarCache> m_chatBarCache;
bool completionVisible = false;
QColor m_mentionColor;

View File

@@ -231,8 +231,11 @@ bool EventHandler::isHidden()
}
}
if (m_event->isStateEvent() && eventCast<const StateEvent>(m_event)->repeatsState()) {
return true;
if (m_event->isStateEvent()) {
auto *stateEvent = eventCast<const StateEvent>(m_event);
if (stateEvent && stateEvent->repeatsState()) {
return true;
}
}
// isReplacement?
@@ -901,28 +904,6 @@ QVariantMap EventHandler::getReplyMediaInfo() const
return getMediaInfoForEvent(replyPtr);
}
bool EventHandler::isThreaded() const
{
return (m_event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
&& m_event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls)
|| (!m_event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && m_event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls));
}
QString EventHandler::threadRoot() const
{
// Get the thread root ID from m.relates_to if it exists.
if (m_event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
&& m_event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls) {
return m_event->contentPart<QJsonObject>("m.relates_to"_ls)["event_id"_ls].toString();
}
// For thread root events they have an m.relations in the unsigned part with a m.thread object.
// If so return the event ID as it is the root.
if (!m_event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && m_event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls)) {
return getId();
}
return {};
}
float EventHandler::getLatitude() const
{
const auto geoUri = m_event->contentJson()["geo_uri"_ls].toString();

View File

@@ -326,20 +326,6 @@ public:
*/
QVariantMap getReplyMediaInfo() const;
/**
* @brief Whether the message is part of a thread.
*
* i.e. There is a rel_type of m.thread.
*/
bool isThreaded() const;
/**
* @brief Return the Matrix ID of the thread's root message.
*
* Empty if this not part of a thread.
*/
QString threadRoot() const;
/**
* @brief Return the latitude for the event.
*

View File

@@ -3,7 +3,7 @@
#pragma once
#include <QList>
#include <QVector>
#include <Quotient/events/eventcontent.h>
#include <Quotient/events/stateevent.h>
@@ -60,7 +60,7 @@ public:
*
* @sa ImagePackImage
*/
QList<ImagePackEventContent::ImagePackImage> images;
QVector<ImagePackEventContent::ImagePackImage> images;
explicit ImagePackEventContent(const QJsonObject &o);

View File

@@ -12,8 +12,7 @@ class LocationHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
QML_UNCREATABLE("")
public:
/** Unite two rectanlges. */
Q_INVOKABLE static QRectF unite(const QRectF &r1, const QRectF &r2);

View File

@@ -212,7 +212,7 @@ void filter(QLoggingCategory *category)
void initLogging()
{
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtInfoMsg).isEnabled(QtDebugMsg);
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtDebugMsg).isEnabled(QtDebugMsg);
oldCategoryFilter = QLoggingCategory::installFilter(filter);
oldHandler = qInstallMessageHandler(messageHandler);
sInstance->setOrigHandler(oldHandler);

View File

@@ -7,10 +7,10 @@
#include <QAbstractListModel>
#include <QCoroTask>
#include <QList>
#include <QObject>
#include <QPointer>
#include <QQmlEngine>
#include <QVector>
#include <Quotient/connection.h>

View File

@@ -3,7 +3,6 @@
#include "actionsmodel.h"
#include "chatbarcache.h"
#include "controller.h"
#include "neochatroom.h"
#include "roommanager.h"
@@ -20,7 +19,7 @@ QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls
"#00d4ff"_ls, "#00aaff"_ls, "#007fff"_ls, "#0055ff"_ls, "#002bff"_ls, "#0000ff"_ls, "#2a00ff"_ls, "#5500ff"_ls, "#7f00ff"_ls,
"#aa00ff"_ls, "#d400ff"_ls, "#ff00ff"_ls, "#ff00d4"_ls, "#ff00aa"_ls, "#ff0080"_ls, "#ff0055"_ls, "#ff002b"_ls, "#ff0000"_ls};
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
room->connection()->leaveRoom(room);
@@ -46,7 +45,7 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
return QString();
};
auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
auto roomNickLambda = [](const QString &text, NeoChatRoom *room) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
} else {
@@ -55,10 +54,10 @@ auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *)
return QString();
};
QList<ActionsModel::Action> actions{
QVector<ActionsModel::Action> actions{
Action{
QStringLiteral("shrug"),
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
[](const QString &message, NeoChatRoom *) {
return QStringLiteral("¯\\\\_(ツ)_/¯ %1").arg(message);
},
true,
@@ -68,7 +67,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("lenny"),
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
[](const QString &message, NeoChatRoom *) {
return QStringLiteral("( ͡° ͜ʖ ͡°) %1").arg(message);
},
true,
@@ -78,7 +77,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("tableflip"),
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
[](const QString &message, NeoChatRoom *) {
return QStringLiteral("(╯°□°)╯︵ ┻━┻ %1").arg(message);
},
true,
@@ -88,7 +87,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("unflip"),
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
[](const QString &message, NeoChatRoom *) {
return QStringLiteral("┬──┬ ( ゜-゜ノ) %1").arg(message);
},
true,
@@ -98,7 +97,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("rainbow"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
[](const QString &text, NeoChatRoom *room) {
QString rainbowText;
for (int i = 0; i < text.length(); i++) {
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
@@ -107,8 +106,8 @@ QList<ActionsModel::Action> actions{
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Text,
chatBarCache->replyId(),
chatBarCache->editId());
room->chatBoxReplyId(),
room->chatBoxEditId());
return QString();
},
false,
@@ -118,7 +117,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("rainbowme"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
[](const QString &text, NeoChatRoom *room) {
QString rainbowText;
for (int i = 0; i < text.length(); i++) {
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
@@ -127,8 +126,8 @@ QList<ActionsModel::Action> actions{
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Emote,
chatBarCache->replyId(),
chatBarCache->editId());
room->chatBoxReplyId(),
room->chatBoxEditId());
return QString();
},
false,
@@ -138,7 +137,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("plain"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
room->postMessage(text, text.toHtmlEscaped(), RoomMessageEvent::MsgType::Text, {}, {});
return QString();
},
@@ -149,13 +148,13 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("spoiler"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
[](const QString &text, NeoChatRoom *room) {
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
room->postMessage(QStringLiteral("/spoiler %1").arg(text),
QStringLiteral("<span data-mx-spoiler>%1</span>").arg(text),
RoomMessageEvent::MsgType::Text,
chatBarCache->replyId(),
chatBarCache->editId());
room->chatBoxReplyId(),
room->chatBoxEditId());
return QString();
},
false,
@@ -165,7 +164,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("me"),
[](const QString &text, NeoChatRoom *, ChatBarCache *) {
[](const QString &text, NeoChatRoom *) {
return text;
},
true,
@@ -175,7 +174,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("notice"),
[](const QString &text, NeoChatRoom *, ChatBarCache *) {
[](const QString &text, NeoChatRoom *) {
return text;
},
true,
@@ -185,7 +184,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("invite"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -221,7 +220,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("join"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
@@ -245,7 +244,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("knock"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
auto parts = text.split(QLatin1String(" "));
QString roomName = parts[0];
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
@@ -277,7 +276,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("j"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
@@ -316,7 +315,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("nick"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
} else {
@@ -347,7 +346,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("ignore"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -375,7 +374,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("unignore"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -403,8 +402,9 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("react"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
if (chatBarCache->replyId().isEmpty()) {
[](const QString &text, NeoChatRoom *room) {
QString replyEventId = room->chatBoxReplyId();
if (replyEventId.isEmpty()) {
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
const auto &evt = **it;
if (const auto event = eventCast<const RoomMessageEvent>(&evt)) {
@@ -413,7 +413,7 @@ QList<ActionsModel::Action> actions{
}
}
}
room->toggleReaction(chatBarCache->replyId(), text);
room->toggleReaction(replyEventId, text);
return QString();
},
false,
@@ -423,7 +423,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("ban"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
auto parts = text.split(QLatin1String(" "));
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
@@ -462,7 +462,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("unban"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -495,7 +495,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("kick"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
auto parts = text.split(QLatin1String(" "));
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
@@ -574,7 +574,7 @@ QHash<int, QByteArray> ActionsModel::roleNames() const
};
}
QList<Action> &ActionsModel::allActions() const
QVector<Action> &ActionsModel::allActions() const
{
return actions;
}

View File

@@ -7,7 +7,6 @@
#include <QAbstractListModel>
#include <Quotient/events/roommessageevent.h>
class ChatBarCache;
class NeoChatRoom;
/**
@@ -29,7 +28,7 @@ public:
/**
* @brief The function to execute when the action is triggered.
*/
std::function<QString(const QString &, NeoChatRoom *, ChatBarCache *)> handle;
std::function<QString(const QString &, NeoChatRoom *)> handle;
/**
* @brief Whether the action is a message type action.
*
@@ -88,7 +87,7 @@ public:
/**
* @brief Return a vector with all supported actions.
*/
QList<Action> &allActions() const;
QVector<Action> &allActions() const;
private:
ActionsModel() = default;

View File

@@ -98,6 +98,6 @@ Q_SIGNALS:
private:
void fetchDevices();
QList<Quotient::Device> m_devices;
QVector<Quotient::Device> m_devices;
QPointer<Quotient::Connection> m_connection;
};

View File

@@ -14,8 +14,6 @@
EmojiModel::EmojiModel(QObject *parent)
: QAbstractListModel(parent)
, m_config(KSharedConfig::openStateConfig())
, m_configGroup(KConfigGroup(m_config, QStringLiteral("Editor")))
{
if (_emojis.isEmpty()) {
#include "emojis.h"
@@ -63,9 +61,9 @@ QHash<int, QByteArray> EmojiModel::roleNames() const
return {{ShortNameRole, "shortName"}, {UnicodeRole, "unicode"}};
}
QStringList EmojiModel::lastUsedEmojis() const
QVariantList EmojiModel::history() const
{
return m_configGroup.readEntry(QStringLiteral("lastUsedEmojis"), QStringList());
return m_settings.value(QStringLiteral("Editor/emojis"), QVariantList()).toList();
}
QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
@@ -95,21 +93,19 @@ QVariantList EmojiModel::filterModelNoCustom(const QString &filter, bool limit)
void EmojiModel::emojiUsed(const QVariant &modelData)
{
auto list = lastUsedEmojis();
const auto emoji = modelData.value<Emoji>();
QVariantList list = history();
auto it = list.begin();
while (it != list.end()) {
if (*it == emoji.shortName) {
if ((*it).value<Emoji>().unicode == modelData.value<Emoji>().unicode) {
it = list.erase(it);
} else {
it++;
}
}
list.push_front(emoji.shortName);
m_configGroup.writeEntry(QStringLiteral("lastUsedEmojis"), list);
list.push_front(modelData);
m_settings.setValue(QStringLiteral("Editor/emojis"), list);
Q_EMIT historyChanged();
}
@@ -117,11 +113,11 @@ void EmojiModel::emojiUsed(const QVariant &modelData)
QVariantList EmojiModel::emojis(Category category) const
{
if (category == History) {
return emojiHistory();
return history();
}
if (category == HistoryNoCustom) {
QVariantList list;
for (const auto &e : emojiHistory()) {
for (const auto &e : history()) {
auto emoji = qvariant_cast<Emoji>(e);
if (!emoji.isCustom) {
list.append(e);
@@ -221,19 +217,4 @@ QVariantList EmojiModel::categoriesWithCustom() const
return cats;
}
QVariantList EmojiModel::emojiHistory() const
{
QVariantList list;
for (const auto &historicEmoji : lastUsedEmojis()) {
for (const auto &emojiCategory : _emojis) {
for (const auto &emoji : emojiCategory) {
if (qvariant_cast<Emoji>(emoji).shortName == historicEmoji) {
list.append(emoji);
}
}
}
}
return list;
}
#include "moc_emojimodel.cpp"

View File

@@ -3,11 +3,10 @@
#pragma once
#include <KConfigGroup>
#include <KSharedConfig>
#include <QAbstractListModel>
#include <QObject>
#include <QQmlEngine>
#include <QSettings>
struct Emoji {
Emoji(QString unicode, QString shortname, bool isCustom = false)
@@ -24,6 +23,21 @@ struct Emoji {
}
Emoji() = default;
friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
{
arch << object.unicode;
arch << object.shortName;
return arch;
}
friend QDataStream &operator>>(QDataStream &arch, Emoji &object)
{
arch >> object.unicode;
arch >> object.shortName;
object.isCustom = object.unicode.startsWith(QStringLiteral("image://"));
return arch;
}
QString unicode;
QString shortName;
QString description;
@@ -49,6 +63,11 @@ class EmojiModel : public QAbstractListModel
QML_ELEMENT
QML_SINGLETON
/**
* @brief Return a list of recently used emojis.
*/
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
/**
* @brief Return a list of emoji categories.
*
@@ -158,11 +177,7 @@ public:
*/
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
/**
* @brief Return a list of the last used emoji shortnames
*/
QStringList lastUsedEmojis() const;
Q_INVOKABLE QVariantList history() const;
QVariantList categories() const;
QVariantList categoriesWithCustom() const;
@@ -175,10 +190,7 @@ public Q_SLOTS:
private:
static QHash<Category, QVariantList> _emojis;
/// Returns QVariants containing the last used Emojis
QVariantList emojiHistory() const;
KSharedConfig::Ptr m_config;
KConfigGroup m_configGroup;
// TODO: Port away from QSettings
QSettings m_settings;
EmojiModel(QObject *parent = nullptr);
};

View File

@@ -151,7 +151,7 @@ void ImagePacksModel::setShowEmoticons(bool showEmoticons)
m_showEmoticons = showEmoticons;
Q_EMIT showEmoticonsChanged();
}
QList<Quotient::ImagePackEventContent::ImagePackImage> ImagePacksModel::images(int index)
QVector<Quotient::ImagePackEventContent::ImagePackImage> ImagePacksModel::images(int index)
{
if (index < 0 || index >= m_events.size()) {
return {};

View File

@@ -5,9 +5,9 @@
#include "events/imagepackevent.h"
#include <QAbstractListModel>
#include <QList>
#include <QPointer>
#include <QQmlEngine>
#include <QVector>
class NeoChatRoom;
@@ -86,7 +86,7 @@ public:
/**
* @brief Return a vector of the images in the pack at the given index.
*/
[[nodiscard]] QList<Quotient::ImagePackEventContent::ImagePackImage> images(int index);
[[nodiscard]] QVector<Quotient::ImagePackEventContent::ImagePackImage> images(int index);
Q_SIGNALS:
void roomChanged();
@@ -96,7 +96,7 @@ Q_SIGNALS:
private:
QPointer<NeoChatRoom> m_room;
QList<Quotient::ImagePackEventContent> m_events;
QVector<Quotient::ImagePackEventContent> m_events;
bool m_showStickers = true;
bool m_showEmoticons = true;
void reloadImages();

View File

@@ -35,7 +35,7 @@ void KeywordNotificationRuleModel::updateNotificationRules(const QString &type)
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"].toObject());
const QList<Quotient::PushRule> contentRules = ruleData.content;
const QVector<Quotient::PushRule> contentRules = ruleData.content;
beginResetModel();
m_notificationRules.clear();
@@ -78,11 +78,11 @@ void KeywordNotificationRuleModel::addKeyword(const QString &keyword)
NotificationsManager::instance().initializeKeywordNotificationAction();
}
const QList<QVariant> actions = NotificationsManager::instance().getKeywordNotificationActions();
const QVector<QVariant> actions = NotificationsManager::instance().getKeywordNotificationActions();
auto job = Controller::instance()
.activeConnection()
->callApi<Quotient::SetPushRuleJob>("global", "content", keyword, actions, "", "", QList<Quotient::PushCondition>(), keyword);
->callApi<Quotient::SetPushRuleJob>("global", "content", keyword, actions, "", "", QVector<Quotient::PushCondition>(), keyword);
connect(job, &Quotient::BaseJob::success, this, [this, keyword]() {
beginInsertRows(QModelIndex(), m_notificationRules.count(), m_notificationRules.count());
m_notificationRules.append(keyword);

View File

@@ -47,8 +47,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
roles[ReplyDelegateTypeRole] = "replyDelegateType";
roles[ReplyDisplayRole] = "replyDisplay";
roles[ReplyMediaInfoRole] = "replyMediaInfo";
roles[IsThreadedRole] = "isThreaded";
roles[ThreadRootRole] = "threadRoot";
roles[ShowAuthorRole] = "showAuthor";
roles[ShowSectionRole] = "showSection";
roles[ReadMarkersRole] = "readMarkers";
@@ -268,7 +266,7 @@ int MessageEventModel::timelineBaseIndex() const
return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0;
}
void MessageEventModel::refreshEventRoles(int row, const QList<int> &roles)
void MessageEventModel::refreshEventRoles(int row, const QVector<int> &roles)
{
const auto idx = index(row);
Q_EMIT dataChanged(idx, idx, roles);
@@ -312,7 +310,7 @@ void MessageEventModel::moveReadMarker(const QString &toEventId)
endMoveRows();
}
int MessageEventModel::refreshEventRoles(const QString &id, const QList<int> &roles)
int MessageEventModel::refreshEventRoles(const QString &id, const QVector<int> &roles)
{
// On 64-bit platforms, difference_type for std containers is long long
// but Qt uses int throughout its interfaces; hence casting to int below.
@@ -588,14 +586,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return eventHandler.getReplyMediaInfo();
}
if (role == IsThreadedRole) {
return eventHandler.isThreaded();
}
if (role == ThreadRootRole) {
return eventHandler.threadRoot();
}
if (role == ShowAuthorRole) {
for (auto r = row + 1; r < rowCount(); ++r) {
auto i = index(r);

View File

@@ -63,9 +63,6 @@ public:
ReplyDisplayRole, /**< The body of the message that was replied to. */
ReplyMediaInfoRole, /**< The media info of the message that was replied to. */
IsThreadedRole,
ThreadRootRole,
ShowAuthorRole, /**< Whether the author's name should be shown. */
ShowSectionRole, /**< Whether the section header should be shown. */
@@ -140,8 +137,8 @@ private:
void fetchMore(const QModelIndex &parent) override;
void refreshLastUserEvents(int baseTimelineRow);
void refreshEventRoles(int row, const QList<int> &roles = {});
int refreshEventRoles(const QString &eventId, const QList<int> &roles = {});
void refreshEventRoles(int row, const QVector<int> &roles = {});
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});
void moveReadMarker(const QString &toEventId);
void createEventObjects(const Quotient::RoomMessageEvent *event);

View File

@@ -124,20 +124,6 @@ void PublicRoomListModel::setKeyword(const QString &value)
Q_EMIT hasMoreChanged();
}
bool PublicRoomListModel::showOnlySpaces() const
{
return m_showOnlySpaces;
}
void PublicRoomListModel::setShowOnlySpaces(bool showOnlySpaces)
{
if (showOnlySpaces == m_showOnlySpaces) {
return;
}
m_showOnlySpaces = showOnlySpaces;
Q_EMIT showOnlySpacesChanged();
}
void PublicRoomListModel::next(int count)
{
if (count < 1) {
@@ -150,11 +136,7 @@ void PublicRoomListModel::next(int count)
return;
}
QStringList roomTypes;
if (m_showOnlySpaces) {
roomTypes += QLatin1String("m.space");
}
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword, roomTypes});
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword, {}});
Q_EMIT loadingChanged();
connect(job, &BaseJob::finished, this, [this] {

View File

@@ -45,11 +45,6 @@ class PublicRoomListModel : public QAbstractListModel
*/
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
/**
* @brief Whether only space rooms should be shown.
*/
Q_PROPERTY(bool showOnlySpaces READ showOnlySpaces WRITE setShowOnlySpaces NOTIFY showOnlySpacesChanged)
/**
* @brief Whether the model has more items to load.
*/
@@ -108,9 +103,6 @@ public:
[[nodiscard]] QString keyword() const;
void setKeyword(const QString &value);
[[nodiscard]] bool showOnlySpaces() const;
void setShowOnlySpaces(bool showOnlySpaces);
[[nodiscard]] bool hasMore() const;
[[nodiscard]] bool loading() const;
@@ -126,13 +118,12 @@ private:
Quotient::Connection *m_connection = nullptr;
QString m_server;
QString m_keyword;
bool m_showOnlySpaces = false;
bool attempted = false;
bool m_loading = false;
QString nextBatch;
QList<Quotient::PublicRoomsChunk> rooms;
QVector<Quotient::PublicRoomsChunk> rooms;
Quotient::QueryPublicRoomsJob *job = nullptr;
@@ -140,7 +131,6 @@ Q_SIGNALS:
void connectionChanged();
void serverChanged();
void keywordChanged();
void showOnlySpacesChanged();
void hasMoreChanged();
void loadingChanged();
};

View File

@@ -106,7 +106,7 @@ void PushRuleModel::updateNotificationRules(const QString &type)
endResetModel();
}
void PushRuleModel::setRules(QList<Quotient::PushRule> rules, PushNotificationKind::Kind kind)
void PushRuleModel::setRules(QVector<Quotient::PushRule> rules, PushNotificationKind::Kind kind)
{
for (const auto &rule : rules) {
QString roomId;
@@ -307,8 +307,8 @@ void PushRuleModel::setPushRuleAction(const QString &id, PushNotificationAction:
void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
{
PushNotificationKind::Kind kind = PushNotificationKind::Content;
const QList<QVariant> actions = actionToVariant(m_defaultKeywordAction);
QList<Quotient::PushCondition> pushConditions;
const QVector<QVariant> actions = actionToVariant(m_defaultKeywordAction);
QVector<Quotient::PushCondition> pushConditions;
if (!roomId.isEmpty()) {
kind = PushNotificationKind::Override;
@@ -369,7 +369,7 @@ void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QStrin
void PushRuleModel::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action)
{
QList<QVariant> actions;
QVector<QVariant> actions;
if (ruleId == QStringLiteral(".m.rule.call")) {
actions = actionToVariant(action, QStringLiteral("ring"));
} else {
@@ -379,7 +379,7 @@ void PushRuleModel::setNotificationRuleActions(const QString &kind, const QStrin
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleActionsJob>(QStringLiteral("global"), kind, ruleId, actions);
}
PushNotificationAction::Action PushRuleModel::variantToAction(const QList<QVariant> &actions, bool enabled)
PushNotificationAction::Action PushRuleModel::variantToAction(const QVector<QVariant> &actions, bool enabled)
{
bool notify = false;
bool isNoisy = false;
@@ -422,16 +422,16 @@ PushNotificationAction::Action PushRuleModel::variantToAction(const QList<QVaria
}
}
QList<QVariant> PushRuleModel::actionToVariant(PushNotificationAction::Action action, const QString &sound)
QVector<QVariant> PushRuleModel::actionToVariant(PushNotificationAction::Action action, const QString &sound)
{
// The caller should never try to set the state to unknown.
// It exists only as a default state to diable the settings options until the actual state is retrieved from the server.
if (action == PushNotificationAction::Unknown) {
Q_ASSERT(false);
return QList<QVariant>();
return QVector<QVariant>();
}
QList<QVariant> actions;
QVector<QVariant> actions;
if (action != PushNotificationAction::Off) {
actions.append(QStringLiteral("notify"));

View File

@@ -238,14 +238,14 @@ private:
PushNotificationAction::Action m_defaultKeywordAction;
QList<Rule> m_rules;
void setRules(QList<Quotient::PushRule> rules, PushNotificationKind::Kind kind);
void setRules(QVector<Quotient::PushRule> rules, PushNotificationKind::Kind kind);
int getRuleIndex(const QString &ruleId) const;
PushNotificationSection::Section getSection(Quotient::PushRule rule);
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action);
PushNotificationAction::Action variantToAction(const QList<QVariant> &actions, bool enabled);
QList<QVariant> actionToVariant(PushNotificationAction::Action action, const QString &sound = QStringLiteral("default"));
PushNotificationAction::Action variantToAction(const QVector<QVariant> &actions, bool enabled);
QVector<QVariant> actionToVariant(PushNotificationAction::Action action, const QString &sound = QStringLiteral("default"));
};
Q_DECLARE_METATYPE(PushRuleModel *)

View File

@@ -372,7 +372,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
return QVariant();
}
void RoomListModel::refresh(NeoChatRoom *room, const QList<int> &roles)
void RoomListModel::refresh(NeoChatRoom *room, const QVector<int> &roles)
{
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
if (it == m_rooms.end()) {

View File

@@ -158,7 +158,7 @@ private Q_SLOTS:
void doAddRoom(Quotient::Room *room);
void updateRoom(Quotient::Room *room, Quotient::Room *prev);
void deleteRoom(Quotient::Room *room);
void refresh(NeoChatRoom *room, const QList<int> &roles = {});
void refresh(NeoChatRoom *room, const QVector<int> &roles = {});
void refreshNotificationCount();
void refreshHighlightCount();

View File

@@ -11,13 +11,12 @@
#include <KConfig>
#include <KConfigGroup>
#include <KSharedConfig>
ServerListModel::ServerListModel(QObject *parent)
: QAbstractListModel(parent)
{
const auto stateConfig = KSharedConfig::openStateConfig();
const KConfigGroup serverGroup = stateConfig->group(QStringLiteral("Servers"));
KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
KConfigGroup serverGroup(&dataResource, QStringLiteral("Servers"));
QString domain = Controller::instance().activeConnection()->domain();
@@ -92,8 +91,8 @@ int ServerListModel::rowCount(const QModelIndex &parent) const
void ServerListModel::checkServer(const QString &url)
{
const auto stateConfig = KSharedConfig::openStateConfig();
const KConfigGroup serverGroup = stateConfig->group(QStringLiteral("Servers"));
KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
KConfigGroup serverGroup(&dataResource, QStringLiteral("Servers"));
if (!serverGroup.hasKey(url)) {
if (Quotient::isJobPending(m_checkServerJob)) {
@@ -109,8 +108,8 @@ void ServerListModel::checkServer(const QString &url)
void ServerListModel::addServer(const QString &url)
{
const auto stateConfig = KSharedConfig::openStateConfig();
KConfigGroup serverGroup = stateConfig->group(QStringLiteral("Servers"));
KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
KConfigGroup serverGroup(&dataResource, QStringLiteral("Servers"));
if (!serverGroup.hasKey(url)) {
Server newServer = Server{
@@ -126,21 +125,17 @@ void ServerListModel::addServer(const QString &url)
}
serverGroup.writeEntry(url, url);
stateConfig->sync();
}
void ServerListModel::removeServerAtIndex(int row)
{
const auto stateConfig = KSharedConfig::openStateConfig();
KConfigGroup serverGroup = stateConfig->group(QStringLiteral("Servers"));
KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
KConfigGroup serverGroup(&dataResource, QStringLiteral("Servers"));
serverGroup.deleteEntry(data(index(row), UrlRole).toString());
beginRemoveRows(QModelIndex(), row, row);
m_servers.removeAt(row);
endRemoveRows();
stateConfig->sync();
}
QHash<int, QByteArray> ServerListModel::roleNames() const

View File

@@ -12,7 +12,7 @@ SortFilterSpaceListModel::SortFilterSpaceListModel(QObject *parent)
sort(0);
invalidateFilter();
connect(this, &QAbstractProxyModel::sourceModelChanged, this, [this]() {
connect(sourceModel(), &QAbstractListModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, QList<int> roles) {
connect(sourceModel(), &QAbstractListModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, QVector<int> roles) {
if (roles.contains(RoomListModel::IsChildSpaceRole)) {
invalidate();
}

View File

@@ -6,9 +6,9 @@
#include "events/imagepackevent.h"
#include "neochatroom.h"
#include <QAbstractListModel>
#include <QList>
#include <QObject>
#include <QQmlEngine>
#include <QVector>
class ImagePacksModel;
@@ -100,7 +100,7 @@ Q_SIGNALS:
private:
ImagePacksModel *m_model = nullptr;
int m_index = 0;
QList<Quotient::ImagePackEventContent::ImagePackImage> m_images;
QVector<Quotient::ImagePackEventContent::ImagePackImage> m_images;
NeoChatRoom *m_room;
void reloadImages();
};

View File

@@ -103,7 +103,7 @@ private:
bool attempted = false;
QList<Quotient::SearchUserDirectoryJob::User> users;
QVector<Quotient::SearchUserDirectoryJob::User> users;
Quotient::SearchUserDirectoryJob *job = nullptr;
};

View File

@@ -145,7 +145,7 @@ void UserListModel::userRemoved(Quotient::User *user)
}
}
void UserListModel::refreshUser(Quotient::User *user, const QList<int> &roles)
void UserListModel::refreshUser(Quotient::User *user, const QVector<int> &roles)
{
auto pos = findUserPos(user);
if (pos != m_users.size()) {

View File

@@ -89,7 +89,7 @@ Q_SIGNALS:
private Q_SLOTS:
void userAdded(Quotient::User *user);
void userRemoved(Quotient::User *user);
void refreshUser(Quotient::User *user, const QList<int> &roles = {});
void refreshUser(Quotient::User *user, const QVector<int> &roles = {});
void refreshAllUsers();
private:

View File

@@ -158,7 +158,7 @@ void NeoChatConnection::deactivateAccount(const QString &password)
void NeoChatConnection::createRoom(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
{
QList<CreateRoomJob::StateEvent> initialStateEvents;
QVector<CreateRoomJob::StateEvent> initialStateEvents;
if (!parent.isEmpty()) {
initialStateEvents.append(CreateRoomJob::StateEvent{
"m.space.parent"_ls,
@@ -190,7 +190,7 @@ void NeoChatConnection::createRoom(const QString &name, const QString &topic, co
void NeoChatConnection::createSpace(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
{
QList<CreateRoomJob::StateEvent> initialStateEvents;
QVector<CreateRoomJob::StateEvent> initialStateEvents;
if (!parent.isEmpty()) {
initialStateEvents.append(CreateRoomJob::StateEvent{
"m.space.parent"_ls,

View File

@@ -39,7 +39,6 @@
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include "chatbarcache.h"
#include "clipboard.h"
#include "controller.h"
#include "eventhandler.h"
@@ -66,9 +65,6 @@ using namespace Quotient;
NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinState)
: Room(connection, std::move(roomId), joinState)
{
m_mainCache = new ChatBarCache(this);
m_editCache = new ChatBarCache(this);
connect(connection, &Connection::accountDataChanged, this, &NeoChatRoom::updatePushNotificationState);
connect(this, &Room::fileTransferCompleted, this, [this] {
setFileUploadingProgress(0);
@@ -121,8 +117,6 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
});
connect(this, &Room::changed, this, [this] {
Q_EMIT canEncryptRoomChanged();
Q_EMIT parentIdsChanged();
Q_EMIT canonicalParentChanged();
});
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
connect(this, &Room::changed, this, [this]() {
@@ -316,7 +310,7 @@ void NeoChatRoom::cacheLastEvent()
if (event != nullptr) {
auto &roomLastMessageProvider = RoomLastMessageProvider::self();
auto eventJson = QJsonDocument(event->fullJson()).toJson(QJsonDocument::Compact);
auto eventJson = QJsonDocument(event->fullJson()).toJson();
roomLastMessageProvider.write(id(), eventJson);
auto uniqueEvent = loadEvent<RoomEvent>(event->fullJson());
@@ -526,63 +520,20 @@ QString msgTypeToString(MessageEventType msgType)
}
}
void NeoChatRoom::postMessage(const QString &rawText,
const QString &text,
MessageEventType type,
const QString &replyEventId,
const QString &relateToEventId,
const QString &threadRootId)
void NeoChatRoom::postMessage(const QString &rawText, const QString &text, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
{
postHtmlMessage(rawText, text, type, replyEventId, relateToEventId, threadRootId);
postHtmlMessage(rawText, text, type, replyEventId, relateToEventId);
}
void NeoChatRoom::postHtmlMessage(const QString &text,
const QString &html,
MessageEventType type,
const QString &replyEventId,
const QString &relateToEventId,
const QString &threadRootId)
void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, MessageEventType type, const QString &replyEventId, const QString &relateToEventId)
{
bool isReply = !replyEventId.isEmpty();
bool isEdit = !relateToEventId.isEmpty();
bool isThread = !threadRootId.isEmpty();
const auto replyIt = findInTimeline(replyEventId);
if (replyIt == historyEdge()) {
isReply = false;
}
if (isThread) {
EventHandler eventHandler;
eventHandler.setRoom(this);
eventHandler.setEvent(&**replyIt);
const bool isFallingBack = !eventHandler.isThreaded();
// clang-format off
QJsonObject json{
{"msgtype"_ls, msgTypeToString(type)},
{"body"_ls, text},
{"format"_ls, "org.matrix.custom.html"_ls},
{"m.relates_to"_ls,
QJsonObject {
{"rel_type"_ls, "m.thread"_ls},
{"event_id"_ls, threadRootId},
{"is_falling_back"_ls, isFallingBack},
{"m.in_reply_to"_ls,
QJsonObject {
{"event_id"_ls, replyEventId}
}
}
}
},
{"formatted_body"_ls, html}
};
// clang-format on
postJson("m.room.message"_ls, json);
return;
}
if (isEdit) {
QJsonObject json{
{"type"_ls, "m.room.message"_ls},
@@ -1148,125 +1099,6 @@ void NeoChatRoom::clearInvitationNotification()
NotificationsManager::instance().clearInvitationNotification(id());
}
bool NeoChatRoom::hasParent() const
{
return currentState().eventsOfType("m.space.parent"_ls).size() > 0;
}
QList<QString> NeoChatRoom::parentIds() const
{
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
QList<QString> parentIds;
for (const auto &parentEvent : parentEvents) {
if (parentEvent->contentJson().contains("via"_ls) && !parentEvent->contentPart<QJsonArray>("via"_ls).isEmpty()) {
parentIds += parentEvent->stateKey();
}
}
return parentIds;
}
QString NeoChatRoom::canonicalParent() const
{
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
for (const auto &parentEvent : parentEvents) {
if (parentEvent->contentJson().contains("via"_ls) && !parentEvent->contentPart<QJsonArray>("via"_ls).isEmpty()) {
if (parentEvent->contentPart<bool>("canonical"_ls)) {
return parentEvent->stateKey();
}
}
}
return {};
}
void NeoChatRoom::setCanonicalParent(const QString &parentId)
{
if (!canModifyParent(parentId)) {
return;
}
if (const auto &parent = currentState().get("m.space.parent"_ls, parentId)) {
auto content = parent->contentJson();
content.insert("canonical"_ls, true);
setState("m.space.parent"_ls, parentId, content);
} else {
return;
}
// Only one canonical parent can exist so make sure others are set false.
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
for (const auto &parentEvent : parentEvents) {
if (parentEvent->contentPart<bool>("canonical"_ls) && parentEvent->stateKey() != parentId) {
auto content = parentEvent->contentJson();
content.insert("canonical"_ls, false);
setState("m.space.parent"_ls, parentEvent->stateKey(), content);
}
}
}
bool NeoChatRoom::canModifyParent(const QString &parentId) const
{
if (!canSendState("m.space.parent"_ls)) {
return false;
}
// If we can't peek the parent we assume that we neither have permission nor is
// there an existing space child event for this room.
if (auto parent = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
if (!parent->isSpace()) {
return false;
}
// If the user is allowed to set space child events in the parent they are
// allowed to set the space as a parent (even if a space child event doesn't
// exist).
if (parent->canSendState("m.space.child"_ls)) {
return true;
}
// If the parent has a space child event the user can set as a parent (even
// if they don't have permission to set space child events in that parent).
if (parent->currentState().contains("m.space.child"_ls, id())) {
return true;
}
}
return false;
}
void NeoChatRoom::addParent(const QString &parentId, bool canonical, bool setParentChild)
{
if (!canModifyParent(parentId)) {
return;
}
if (canonical) {
// Only one canonical parent can exist so make sure others are set false.
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
for (const auto &parentEvent : parentEvents) {
if (parentEvent->contentPart<bool>("canonical"_ls)) {
auto content = parentEvent->contentJson();
content.insert("canonical"_ls, false);
setState("m.space.parent"_ls, parentEvent->stateKey(), content);
}
}
}
setState("m.space.parent"_ls, parentId, QJsonObject{{"canonical"_ls, canonical}, {"via"_ls, QJsonArray{connection()->domain()}}});
if (setParentChild) {
if (auto parent = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
parent->setState("m.space.child"_ls, id(), QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}});
}
}
}
void NeoChatRoom::removeParent(const QString &parentId)
{
if (!canModifyParent(parentId)) {
return;
}
if (!currentState().contains("m.space.parent"_ls, parentId)) {
return;
}
if (auto parent = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
setState("m.space.parent"_ls, parentId, {});
}
}
bool NeoChatRoom::isSpace()
{
const auto creationEvent = this->creation();
@@ -1277,7 +1109,7 @@ bool NeoChatRoom::isSpace()
return creationEvent->roomType() == RoomType::Space;
}
void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical)
void NeoChatRoom::addChild(const QString &childId, bool setChildParent)
{
if (!isSpace()) {
return;
@@ -1290,19 +1122,7 @@ void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool can
if (setChildParent) {
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
if (child->canSendState("m.space.parent"_ls)) {
child->setState("m.space.parent"_ls, id(), QJsonObject{{"canonical"_ls, canonical}, {"via"_ls, QJsonArray{connection()->domain()}}});
if (canonical) {
// Only one canonical parent can exist so make sure others are set to false.
auto parentEvents = child->currentState().eventsOfType("m.space.parent"_ls);
for (const auto &parentEvent : parentEvents) {
if (parentEvent->contentPart<bool>("canonical"_ls)) {
auto content = parentEvent->contentJson();
content.insert("canonical"_ls, false);
setState("m.space.parent"_ls, parentEvent->stateKey(), content);
}
}
}
child->setState("m.space.parent"_ls, id(), QJsonObject{{"canonical"_ls, true}, {"via"_ls, QJsonArray{connection()->domain()}}});
}
}
}
@@ -1389,7 +1209,7 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
* "don't_notify"
* ]
*/
const QList<QVariant> actions = {"dont_notify"_ls};
const QVector<QVariant> actions = {"dont_notify"_ls};
/**
* Setup the push condition to get all events for the current room
* see https://spec.matrix.org/v1.3/client-server-api/#conditions-1
@@ -1406,7 +1226,7 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
pushCondition.kind = "event_match"_ls;
pushCondition.key = "room_id"_ls;
pushCondition.pattern = id();
const QList<PushCondition> conditions = {pushCondition};
const QVector<PushCondition> conditions = {pushCondition};
// Add new override rule and make sure it's enabled
auto job = Controller::instance()
@@ -1433,9 +1253,9 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
* "don't_notify"
* ]
*/
const QList<QVariant> actions = {"dont_notify"_ls};
const QVector<QVariant> actions = {"dont_notify"_ls};
// No conditions for a room rule
const QList<PushCondition> conditions;
const QVector<PushCondition> conditions;
auto setJob = Controller::instance()
.activeConnection()
@@ -1465,9 +1285,9 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
QJsonObject tweaks;
tweaks.insert("set_tweak"_ls, "sound"_ls);
tweaks.insert("value"_ls, "default"_ls);
const QList<QVariant> actions = {"notify"_ls, tweaks};
const QVector<QVariant> actions = {"notify"_ls, tweaks};
// No conditions for a room rule
const QList<PushCondition> conditions;
const QVector<PushCondition> conditions;
// Add new room rule and make sure enabled
auto setJob = Controller::instance()
@@ -1618,14 +1438,128 @@ void NeoChatRoom::copyEventMedia(const QString &eventId)
}
}
ChatBarCache *NeoChatRoom::mainCache() const
QString NeoChatRoom::chatBoxText() const
{
return m_mainCache;
return m_chatBoxText;
}
ChatBarCache *NeoChatRoom::editCache() const
void NeoChatRoom::setChatBoxText(const QString &text)
{
return m_editCache;
m_chatBoxText = text;
Q_EMIT chatBoxTextChanged();
}
QString NeoChatRoom::editText() const
{
return m_editText;
}
void NeoChatRoom::setEditText(const QString &text)
{
m_editText = text;
Q_EMIT editTextChanged();
}
QString NeoChatRoom::chatBoxReplyId() const
{
return m_chatBoxReplyId;
}
void NeoChatRoom::setChatBoxReplyId(const QString &replyId)
{
if (replyId == m_chatBoxReplyId) {
return;
}
m_chatBoxReplyId = replyId;
Q_EMIT chatBoxReplyIdChanged();
}
QString NeoChatRoom::chatBoxEditId() const
{
return m_chatBoxEditId;
}
void NeoChatRoom::setChatBoxEditId(const QString &editId)
{
if (editId == m_chatBoxEditId) {
return;
}
m_chatBoxEditId = editId;
Q_EMIT chatBoxEditIdChanged();
}
QVariantMap NeoChatRoom::chatBoxReplyUser() const
{
if (m_chatBoxReplyId.isEmpty()) {
return emptyUser;
}
return getUser(user((*findInTimeline(m_chatBoxReplyId))->senderId()));
}
QString NeoChatRoom::chatBoxReplyMessage() const
{
if (m_chatBoxReplyId.isEmpty()) {
return {};
}
EventHandler eventhandler;
eventhandler.setRoom(this);
eventhandler.setEvent(&**findInTimeline(m_chatBoxReplyId));
return eventhandler.getPlainBody();
}
QVariantMap NeoChatRoom::chatBoxEditUser() const
{
if (m_chatBoxEditId.isEmpty()) {
return emptyUser;
}
return getUser(user((*findInTimeline(m_chatBoxEditId))->senderId()));
}
QString NeoChatRoom::chatBoxEditMessage() const
{
if (m_chatBoxEditId.isEmpty()) {
return {};
}
EventHandler eventhandler;
eventhandler.setRoom(this);
if (auto event = findInTimeline(m_chatBoxEditId); event != historyEdge()) {
eventhandler.setEvent(&**event);
return eventhandler.getPlainBody();
}
return {};
}
QString NeoChatRoom::chatBoxAttachmentPath() const
{
return m_chatBoxAttachmentPath;
}
void NeoChatRoom::setChatBoxAttachmentPath(const QString &attachmentPath)
{
m_chatBoxAttachmentPath = attachmentPath;
Q_EMIT chatBoxAttachmentPathChanged();
}
QVector<Mention> *NeoChatRoom::mentions()
{
return &m_mentions;
}
QVector<Mention> *NeoChatRoom::editMentions()
{
return &m_editMentions;
}
QString NeoChatRoom::savedText() const
{
return m_savedText;
}
void NeoChatRoom::setSavedText(const QString &savedText)
{
m_savedText = savedText;
}
void NeoChatRoom::replyLastMessage()
@@ -1654,7 +1588,7 @@ void NeoChatRoom::replyLastMessage()
// For any message that isn't an edit return the id of the current message
eventId = (*it)->id();
}
mainCache()->setReplyId(eventId);
setChatBoxReplyId(eventId);
return;
}
}
@@ -1688,7 +1622,7 @@ void NeoChatRoom::editLastMessage()
// For any message that isn't an edit return the id of the current message
eventId = (*it)->id();
}
editCache()->setEditId(eventId);
setChatBoxEditId(eventId);
return;
}
}

View File

@@ -20,8 +20,6 @@ namespace Quotient
class User;
}
class ChatBarCache;
class PushNotificationState : public QObject
{
Q_OBJECT
@@ -42,6 +40,17 @@ public:
Q_ENUM(State)
};
/**
* @brief Defines a user mention in the current chat or edit text.
*/
struct Mention {
QTextCursor cursor; /**< Contains the mention's text and position in the text. */
QString text; /**< The inserted text of the mention. */
int start = 0; /**< Start position of the mention. */
int position = 0; /**< End position of the mention. */
QString id; /**< The id the mention (used to create link when sending the message). */
};
/**
* @class NeoChatRoom
*
@@ -117,28 +126,6 @@ class NeoChatRoom : public Quotient::Room
*/
Q_PROPERTY(Quotient::User *directChatRemoteUser READ directChatRemoteUser CONSTANT)
/**
* @brief The Matrix IDs of this room's parents.
*
* Empty if no parent space is set.
*/
Q_PROPERTY(QList<QString> parentIds READ parentIds NOTIFY parentIdsChanged)
/**
* @brief The current canonical parent for the room.
*
* Empty if no canonical parent is set. The write method can only be used to
* set an existing parent as canonical; If you wish to add a new parent and set
* it as canonical use the addParent method and pass true to the canonical
* parameter.
*
* Setting will fail if the user doesn't have the required privileges (see
* canModifyParent) or if the given room ID is not a parent room.
*
* @sa canModifyParent, addParent
*/
Q_PROPERTY(QString canonicalParent READ canonicalParent WRITE setCanonicalParent NOTIFY canonicalParentChanged)
/**
* @brief If the room is a space.
*/
@@ -302,14 +289,94 @@ class NeoChatRoom : public Quotient::Room
Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged)
/**
* @brief The cache for the main chat bar in the room.
* @brief The current text in the chatbox for the room.
*
* Due to problems with QTextDocument, unlike the other properties here,
* chatBoxText is *not* used to store the text when switching rooms.
*/
Q_PROPERTY(ChatBarCache *mainCache READ mainCache CONSTANT)
Q_PROPERTY(QString chatBoxText READ chatBoxText WRITE setChatBoxText NOTIFY chatBoxTextChanged)
/**
* @brief The cache for the edit chat bar in the room.
* @brief The text for any message currently being edited in the room.
*/
Q_PROPERTY(ChatBarCache *editCache READ editCache CONSTANT)
Q_PROPERTY(QString editText READ editText WRITE setEditText NOTIFY editTextChanged)
/**
* @brief The event id of a message being replied to.
*
* Will be QString() if not replying to a message.
*/
Q_PROPERTY(QString chatBoxReplyId READ chatBoxReplyId WRITE setChatBoxReplyId NOTIFY chatBoxReplyIdChanged)
/**
* @brief The event id of a message being edited.
*
* Will be QString() if not editing to a message.
*/
Q_PROPERTY(QString chatBoxEditId READ chatBoxEditId WRITE setChatBoxEditId NOTIFY chatBoxEditIdChanged)
/**
* @brief Get the user for the message being replied to.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
*
* Returns an empty user if not replying to a message.
*
* The user QVariantMap has the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User
*/
Q_PROPERTY(QVariantMap chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
/**
* @brief The content of the message being replied to.
*
* Will be QString() if not replying to a message.
*/
Q_PROPERTY(QString chatBoxReplyMessage READ chatBoxReplyMessage NOTIFY chatBoxReplyIdChanged)
/**
* @brief Get the user for the message being edited.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
*
* Returns an empty user if not replying to a message.
*
* The user QVariantMap has the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User
*/
Q_PROPERTY(QVariantMap chatBoxEditUser READ chatBoxEditUser NOTIFY chatBoxEditIdChanged)
/**
* @brief The content of the message being edited.
*
* Will be QString() if not editing a message.
*/
Q_PROPERTY(QString chatBoxEditMessage READ chatBoxEditMessage NOTIFY chatBoxEditIdChanged)
/**
* @brief The file path of the attachment to be sent.
*/
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
public:
/**
@@ -520,60 +587,10 @@ public:
Quotient::User *directChatRemoteUser() const;
/**
* @brief Whether this room has one or more parent spaces set.
*/
Q_INVOKABLE bool hasParent() const;
QList<QString> parentIds() const;
QString canonicalParent() const;
void setCanonicalParent(const QString &parentId);
/**
* @brief Whether the local user has permission to set the given space as a parent.
*
* @note This follows the rules determined in the Matrix spec
* https://spec.matrix.org/v1.7/client-server-api/#mspaceparent-relationships
*/
Q_INVOKABLE bool canModifyParent(const QString &parentId) const;
/**
* @brief Add the given room as a parent.
*
* Will fail if the user doesn't have the required privileges (see
* canModifyParent()).
*
* @sa canModifyParent()
*/
Q_INVOKABLE void addParent(const QString &parentId, bool canonical = false, bool setParentChild = false);
/**
* @brief Remove the given room as a parent.
*
* Will fail if the user doesn't have the required privileges (see
* canModifyParent()).
*
* @sa canModifyParent()
*/
Q_INVOKABLE void removeParent(const QString &parentId);
[[nodiscard]] bool isSpace();
/**
* @brief Add the given room as a child.
*
* Will fail if the user doesn't have the required privileges or this room is
* not a space.
*/
Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false, bool canonical = false);
Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false);
/**
* @brief Remove the given room as a child.
*
* Will fail if the user doesn't have the required privileges or this room is
* not a space.
*/
Q_INVOKABLE void removeChild(const QString &childId, bool unsetChildParent = false);
bool isInvite() const;
@@ -716,9 +733,46 @@ public:
[[nodiscard]] int spaceParentPowerLevel() const;
void setSpaceParentPowerLevel(const int &newPowerLevel);
ChatBarCache *mainCache() const;
QString chatBoxText() const;
void setChatBoxText(const QString &text);
ChatBarCache *editCache() const;
QString editText() const;
void setEditText(const QString &text);
QString chatBoxReplyId() const;
void setChatBoxReplyId(const QString &replyId);
QVariantMap chatBoxReplyUser() const;
QString chatBoxReplyMessage() const;
QString chatBoxEditId() const;
void setChatBoxEditId(const QString &editId);
QVariantMap chatBoxEditUser() const;
QString chatBoxEditMessage() const;
QString chatBoxAttachmentPath() const;
void setChatBoxAttachmentPath(const QString &attachmentPath);
/**
* @brief Retrieve the mentions for the current chatbox text.
*/
QVector<Mention> *mentions();
/**
* @brief Retrieve the mentions for the current edit text.
*/
QVector<Mention> *editMentions();
/**
* @brief Get the saved chatbox text for the room.
*/
QString savedText() const;
/**
* @brief Save the chatbox text for the room.
*/
void setSavedText(const QString &savedText);
/**
* @brief Reply to the last message sent in the timeline.
@@ -790,9 +844,14 @@ private:
std::unique_ptr<Quotient::RoomEvent> m_cachedEvent;
ChatBarCache *m_mainCache;
ChatBarCache *m_editCache;
QString m_chatBoxText;
QString m_editText;
QString m_chatBoxReplyId;
QString m_chatBoxEditId;
QString m_chatBoxAttachmentPath;
QVector<Mention> m_mentions;
QVector<Mention> m_editMentions;
QString m_savedText;
QCache<QString, PollHandler> m_polls;
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
@@ -808,13 +867,16 @@ Q_SIGNALS:
void fileUploadingProgressChanged();
void backgroundChanged();
void readMarkerLoadedChanged();
void parentIdsChanged();
void canonicalParentChanged();
void lastActiveTimeChanged();
void isInviteChanged();
void displayNameChanged();
void pushNotificationStateChanged(PushNotificationState::State state);
void showMessage(MessageType messageType, const QString &message);
void chatBoxTextChanged();
void editTextChanged();
void chatBoxReplyIdChanged();
void chatBoxEditIdChanged();
void chatBoxAttachmentPathChanged();
void canEncryptRoomChanged();
void joinRuleChanged();
void historyVisibilityChanged();
@@ -884,8 +946,7 @@ public Q_SLOTS:
const QString &cleanedText,
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
const QString &replyEventId = QString(),
const QString &relateToEventId = QString(),
const QString &threadRootId = QString());
const QString &relateToEventId = QString());
/**
* @brief Send an html message to the room.
@@ -900,8 +961,7 @@ public Q_SLOTS:
const QString &html,
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
const QString &replyEventId = QString(),
const QString &relateToEventId = QString(),
const QString &threadRootId = QString());
const QString &relateToEventId = QString());
/**
* @brief Set the room avatar.

View File

@@ -5,9 +5,8 @@
import QtQuick.Layouts
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
import org.kde.coreaddons
FormCard.AboutPage {
title: i18nc("@title:window", "About NeoChat")
aboutData: AboutData
aboutData: About
}

View File

@@ -49,7 +49,7 @@ QQC2.Menu {
QQC2.MenuItem {
text: i18n("Logout")
icon.name: "list-remove-user"
onTriggered: confirmLogoutDialogComponent.createObject(applicationWindow().overlay).open()
onTriggered: confirmLogoutDialogComponent.createObject(QQC2.ApplicationWindow.overlay).open()
}
Component {

View File

@@ -77,7 +77,7 @@ FormCard.FormCardPage {
Component {
id: confirmLogoutDialogComponent
ConfirmLogoutDialog {
connection: accountDelegate.connection
connection: model.connection
onAccepted: {
if (AccountRegistry.accountCount === 1) {
root.Window.window.close()

View File

@@ -39,7 +39,6 @@ QQC2.Control {
* @brief The current room that user is viewing.
*/
required property NeoChatRoom currentRoom
onCurrentRoomChanged: _private.chatBarCache = currentRoom.mainCache
/**
* @brief The QQC2.TextArea object.
@@ -63,7 +62,7 @@ QQC2.Control {
property bool isBusy: root.currentRoom && root.currentRoom.hasFileUploading
// Matrix does not allow sending attachments in replies
visible: _private.chatBarCache.isReplying && _private.chatBarCache.attachmentPath.length === 0
visible: root.currentRoom.chatBoxReplyId.length === 0 && root.currentRoom.chatBoxAttachmentPath.length === 0
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
displayHint: Kirigami.DisplayHint.IconOnly
@@ -77,7 +76,7 @@ QQC2.Control {
if (!path) {
return;
}
_private.chatBarCache.attachmentPath = path;
root.currentRoom.chatBoxAttachmentPath = path;
})
fileDialog.open()
}
@@ -170,7 +169,7 @@ QQC2.Control {
leftPadding: LayoutMirroring.enabled ? actionsRow.width : Kirigami.Units.largeSpacing
rightPadding: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : actionsRow.width + x * 2 + Kirigami.Units.largeSpacing * 2
placeholderText: root.currentRoom.usesEncryption ? i18n("Send an encrypted message…") : _private.chatBarCache.attachmentPath.length > 0 ? i18n("Set an attachment caption...") : i18n("Send a message…")
placeholderText: root.currentRoom.usesEncryption ? i18n("Send an encrypted message…") : root.currentRoom.chatBoxAttachmentPath.length > 0 ? i18n("Set an attachment caption...") : i18n("Send a message…")
verticalAlignment: TextEdit.AlignVCenter
wrapMode: Text.Wrap
@@ -190,7 +189,7 @@ QQC2.Control {
root.currentRoom.sendTypingNotification(textExists)
textExists ? repeatTimer.start() : repeatTimer.stop()
}
_private.chatBarCache.text = text
root.currentRoom.chatBoxText = text
}
onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle)
onSelectedTextChanged: {
@@ -286,25 +285,25 @@ QQC2.Control {
anchors.rightMargin: root.width > chatBarSizeHelper.currentWidth ? 0 : (chatBarScrollView.QQC2.ScrollBar.vertical.visible ? Kirigami.Units.largeSpacing * 3.5 : Kirigami.Units.largeSpacing)
active: visible
visible: _private.chatBarCache.isReplying || _private.chatBarCache.attachmentPath.length > 0
sourceComponent: _private.chatBarCache.isReplying ? replyPane : attachmentPane
visible: root.currentRoom.chatBoxReplyId.length > 0 || root.currentRoom.chatBoxAttachmentPath.length > 0
sourceComponent: root.currentRoom.chatBoxReplyId.length > 0 ? replyPane : attachmentPane
}
Component {
id: replyPane
ReplyPane {
userName: _private.chatBarCache.relationUser.displayName
userColor: _private.chatBarCache.relationUser.color
userAvatar: _private.chatBarCache.relationUser.avatarSource
text: _private.chatBarCache.relationMessage
userName: root.currentRoom.chatBoxReplyUser.displayName
userColor: root.currentRoom.chatBoxReplyUser.color
userAvatar: root.currentRoom.chatBoxReplyUser.avatarSource
text: root.currentRoom.chatBoxReplyMessage
}
}
Component {
id: attachmentPane
AttachmentPane {
attachmentPath: _private.chatBarCache.attachmentPath
attachmentPath: root.currentRoom.chatBoxAttachmentPath
onAttachmentCancelled: {
_private.chatBarCache.attachmentPath = "";
root.currentRoom.chatBoxAttachmentPath = "";
root.forceActiveFocus()
}
}
@@ -350,14 +349,14 @@ QQC2.Control {
anchors.right: parent.right
anchors.rightMargin: (root.width - chatBarSizeHelper.currentWidth) / 2 + Kirigami.Units.largeSpacing + (chatBarScrollView.QQC2.ScrollBar.vertical.visible && !(root.width > chatBarSizeHelper.currentWidth) ? Kirigami.Units.largeSpacing * 2.5 : 0)
visible: _private.chatBarCache.isReplying
visible: root.currentRoom.chatBoxReplyId.length > 0
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
text: i18nc("@action:button", "Cancel reply")
icon.name: "dialog-close"
onTriggered: {
_private.chatBarCache.replyId = "";
_private.chatBarCache.attachmentPath = "";
root.currentRoom.chatBoxReplyId = "";
root.currentRoom.chatBoxAttachmentPath = "";
root.forceActiveFocus()
}
}
@@ -473,16 +472,16 @@ QQC2.Control {
if (localPath.length === 0) {
return false;
}
_private.chatBarCache.attachmentPath = localPath;
root.currentRoom.chatBoxAttachmentPath = localPath;
return true;
}
function postMessage() {
RoomManager.actionsHandler.handleMessageEvent(_private.chatBarCache);
actionsHandler.handleNewMessage();
repeatTimer.stop()
root.currentRoom.markAllMessagesAsRead();
textField.clear();
_private.chatBarCache.replyId = "";
root.currentRoom.chatBoxReplyId = "";
messageSent()
}
@@ -559,7 +558,7 @@ QQC2.Control {
if (!path) {
return;
}
_private.chatBarCache.attachmentPath = path;
root.currentRoom.chatBoxAttachmentPath = path;
})
fileDialog.open()
@@ -582,7 +581,7 @@ QQC2.Control {
if (!Clipboard.saveImage(localPath)) {
return;
}
_private.chatBarCache.attachmentPath = localPath;
root.currentRoom.chatBoxAttachmentPath = localPath;
attachDialog.close();
}
}
@@ -596,10 +595,4 @@ QQC2.Control {
parentWindow: Window.window
}
}
QtObject {
id: _private
property ChatBarCache chatBarCache
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
}
}

View File

@@ -133,6 +133,7 @@ Loader {
onClosed: {
root.closed()
destroy()
}
}
}

View File

@@ -24,7 +24,7 @@ FormCard.FormCardPage {
required property NeoChatConnection connection
signal addChild(string childId, bool setChildParent, bool canonical)
signal addChild(string childId, bool setChildParent)
signal newChild(string childName)
title: isSpace ? i18nc("@title", "Create a Space") : i18nc("@title", "Create a Room")
@@ -214,18 +214,11 @@ FormCard.FormCardPage {
return false;
}
}
FormCard.FormCheckDelegate {
id: makeCanonicalCheck
text: i18n("Make this space the canonical parent")
checked: enabled
enabled: existingOfficialCheck.enabled
}
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Ok")
enabled: chosenRoomDelegate.visible
onClicked: {
root.addChild(chosenRoomDelegate.roomId, existingOfficialCheck.checked, makeCanonicalCheck.checked);
root.addChild(chosenRoomDelegate.roomId, existingOfficialCheck.checked);
root.closeDialog();
}
}

View File

@@ -26,7 +26,7 @@ RowLayout {
if (isJoined) {
RoomManager.enterRoom(root.connection.room(roomId))
} else {
Controller.joinRoom(roomId.length > 0 ? roomId : alias)
Controller.joinRoom(roomId)
}
})
}
@@ -38,7 +38,7 @@ RowLayout {
}
property Kirigami.Action roomAction: Kirigami.Action {
text: i18n("Create a Room")
icon.name: "system-users-symbolic"
icon.name: "system-users"
onTriggered: {
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateRoomDialog.qml", {connection: root.connection}, {title: i18nc("@title", "Create a Room")})
}

View File

@@ -59,8 +59,8 @@ MessageDelegateContextMenu {
text: i18n("Reply")
icon.name: "mail-replied-symbolic"
onTriggered: {
currentRoom.mainCache.replyId = eventId;
currentRoom.editCache.editId = "";
currentRoom.chatBoxReplyId = eventId
currentRoom.chatBoxEditId = ""
RoomManager.requestFullScreenClose()
}
},

View File

@@ -278,90 +278,6 @@ FormCard.FormCardPage {
}
}
}
FormCard.FormHeader {
title: i18n("Official Parent Spaces")
}
FormCard.FormCard {
Repeater {
id: officalParentRepeater
model: root.room.parentIds
delegate: FormCard.FormTextDelegate {
id: officalParentDelegate
required property string modelData
property NeoChatRoom space: root.connection.room(modelData)
text: {
if (space) {
return space.displayName;
} else {
return modelData;
}
}
description: {
if (space) {
if (space.canonicalAlias.length > 0) {
return space.canonicalAlias;
} else {
return modelData;
}
} else {
return "";
}
}
contentItem.children: RowLayout {
QQC2.Label {
visible: root.room.canonicalParent === officalParentDelegate.modelData
text: i18n("Canonical")
}
QQC2.ToolButton {
visible: root.room.canSendState("m.space.parent") && root.room.canonicalParent !== officalParentDelegate.modelData
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
id: canonicalParentAction
text: i18n("Make canonical parent")
icon.name: "checkmark"
onTriggered: root.room.canonicalParent = officalParentDelegate.modelData
}
QQC2.ToolTip {
text: canonicalParentAction.text
delay: Kirigami.Units.toolTipDelay
}
}
QQC2.ToolButton {
visible: officalParentDelegate?.space.canSendState("m.space.child") && root.room.canSendState("m.space.parent")
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
id: removeParentAction
text: i18n("Remove parent")
icon.name: "edit-delete-remove"
onTriggered: root.room.removeParent(officalParentDelegate.modelData)
}
QQC2.ToolTip {
text: removeParentAction.text
delay: Kirigami.Units.toolTipDelay
}
}
}
}
}
FormCard.FormTextDelegate {
visible: officalParentRepeater.count <= 0
text: i18n("This room has no official parent spaces.")
}
FormCard.FormButtonDelegate {
visible: root.room.canSendState("m.space.parent")
text: i18nc("@action:button", "Add new official parent")
onClicked: selectParentDialog.createObject(applicationWindow().overlay).open();
Component {
id: selectParentDialog
SelectParentDialog {
room: root.room
}
}
}
}
Kirigami.InlineMessage {
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
@@ -423,5 +339,6 @@ FormCard.FormCardPage {
}
}
}
}

View File

@@ -82,9 +82,7 @@ ColumnLayout {
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
visible: text.length > 0
text: root.room && root.room.topic ? root.room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : ""
text: root.room && root.room.topic ? root.room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
textFormat: TextEdit.MarkdownText
wrapMode: Text.Wrap

View File

@@ -17,11 +17,6 @@ import org.kde.neochat
QQC2.Control {
id: root
/**
* @brief The current message delegate the actions are being shown on.
*/
property var delegate: null
/**
* @brief The current room that user is viewing.
*/
@@ -30,22 +25,38 @@ QQC2.Control {
/**
* @brief Whether the actions should be shown.
*/
readonly property bool showActions: delegate && delegate.hovered
property bool showActions: false
/**
* @brief Request that the chat bar be focussed.
* @brief Whether the message has been sent from a verified matrix session.
*/
signal focusChatBar()
property bool verified: false
/**
* @brief Whether the edit button should be shown.
*/
property bool editable: false
/**
* @brief The react button has been clicked.
*/
signal reactClicked(string emoji)
/**
* @brief The edit button has been clicked.
*/
signal editClicked()
/**
* @brief The reply button has been clicked.
*/
signal replyClicked()
topPadding: 0
bottomPadding: 0
leftPadding: 0
rightPadding: 0
x: delegate ? delegate.x + delegate.bubbleX : 0
y: delegate ? delegate.mapToItem(parent, 0, 0).y + delegate.bubbleY - height + Kirigami.Units.smallSpacing : 0
width: delegate ? delegate.bubbleWidth : Kirigami.Units.gridUnit * 4
visible: (root.hovered || root.showActions || showActionsTimer.running) && !Kirigami.Settings.isMobile
onVisibleChanged: {
if (visible) {
@@ -67,7 +78,7 @@ QQC2.Control {
source: "security-high"
width: height
height: root.height
visible: root.delegate && root.delegate.verified
visible: root.verified
HoverHandler {
id: hover
}
@@ -89,32 +100,15 @@ QQC2.Control {
onTriggered: emojiDialog.open()
},
Kirigami.Action {
visible: root.delegate && root.delegate.author.isLocalUser && (root.delegate.delegateType === DelegateType.Emote || root.delegate.delegateType === DelegateType.Message)
text: i18n("Edit")
icon.name: "document-edit"
onTriggered: {
root.currentRoom.editCache.editId = root.delegate.eventId;
root.currentRoom.mainCache.replyId = "";
}
onTriggered: root.editClicked()
visible: root.editable
},
Kirigami.Action {
text: i18n("Reply")
icon.name: "mail-replied-symbolic"
onTriggered: {
root.currentRoom.mainCache.replyId = root.delegate.eventId;
root.currentRoom.editCache.editId = "";
root.focusChatBar();
}
},
Kirigami.Action {
text: i18n("Reply in Thread")
icon.name: "dialog-messages"
onTriggered: {
root.currentRoom.mainCache.replyId = root.delegate.eventId;
root.currentRoom.mainCache.threadId = root.delegate.isThreaded ? root.delegate.threadRoot : root.delegate.eventId;
root.currentRoom.editCache.editId = "";
root.focusChatBar();
}
onTriggered: root.replyClicked()
}
]
@@ -122,12 +116,7 @@ QQC2.Control {
id: emojiDialog
currentRoom: root.currentRoom
showQuickReaction: true
onChosen: (emoji) => {
root.currentRoom.toggleReaction(root.delegate.eventId, emoji);
if (!Kirigami.Settings.isMobile) {
root.focusChatBar();
}
}
onChosen: (emoji) => root.reactClicked(emoji)
}
}
}

View File

@@ -17,8 +17,6 @@ Kirigami.ScrollablePage {
required property NeoChatConnection connection
property bool showOnlySpaces: false
property alias keyword: identifierField.text
property string server
@@ -54,9 +52,32 @@ Kirigami.ScrollablePage {
contentItem: RowLayout {
Kirigami.SearchField {
id: identifierField
property bool isRoomAlias: text.match(/#(.+):(.+)/g)
property NeoChatRoom room: isRoomAlias ? connection.roomByAlias(text) : null
property bool isJoined: room != null
Layout.fillWidth: true
placeholderText: i18n("Find a room...")
}
QQC2.Button {
id: joinButton
visible: identifierField.isRoomAlias
text: identifierField.isJoined ? i18n("View") : i18n("Join")
highlighted: true
onClicked: {
if (!identifierField.isJoined) {
Controller.joinRoom(identifierField.text);
// When joining the room, the room will be opened
}
applicationWindow().pageStack.layers.pop();
}
}
QQC2.ComboBox {
id: serverField
@@ -218,7 +239,6 @@ Kirigami.ScrollablePage {
connection: root.connection
server: root.server
keyword: root.keyword
showOnlySpaces: root.showOnlySpaces
}
onContentYChanged: {
@@ -232,26 +252,19 @@ Kirigami.ScrollablePage {
}
}
header: Delegates.RoundedItemDelegate {
Layout.fillWidth: true
onClicked: _private.openManualRoomDialog()
text: i18n("Enter a room address")
icon.name: "compass"
icon.width: Kirigami.Units.gridUnit * 2
icon.height: Kirigami.Units.gridUnit * 2
}
footer: QQC2.ProgressBar {
footer: RowLayout {
width: parent.width
visible: publicRoomsListView.count !== 0 && publicRoomsListView.model.loading
indeterminate: true
padding: Kirigami.Units.largeSpacing * 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
QQC2.ProgressBar {
visible: publicRoomsListView.model.loading && publicRoomsListView.count !== 0
indeterminate: true
padding: Kirigami.Units.largeSpacing * 2
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
}
}
Kirigami.LoadingPlaceholder {
@@ -262,24 +275,7 @@ Kirigami.ScrollablePage {
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
visible: !publicRoomsListView.model.loading && publicRoomsListView.count === 0
text: i18nc("@info:label", "No public rooms found")
}
}
Component {
id: manualRoomDialog
ManualRoomDialog {}
}
QtObject {
id: _private
function openManualRoomDialog() {
let dialog = manualRoomDialog.createObject(applicationWindow().overlay, {connection: root.connection});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
root.closeDialog();
});
dialog.open();
text: i18nc("@info:label", "No rooms found")
}
}
}

View File

@@ -11,9 +11,6 @@ import org.kde.neochat
LoginStep {
id: root
signal closeDialog
FormCard.FormTextDelegate {
text: i18n("Please wait. This might take a little while.")
}
@@ -25,7 +22,7 @@ LoginStep {
Connections {
target: Controller
function onInitiated() {
root.closeDialog()
closeDialog()
}
}
}

Some files were not shown because too many files have changed in this diff Show More