Compare commits
177 Commits
work/redst
...
work/carl/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fab2a81aee | ||
|
|
4daff26747 | ||
|
|
fe09dc23a6 | ||
|
|
9cd4a7416e | ||
|
|
f6e3210b0d | ||
|
|
16d33eb02c | ||
|
|
326512697c | ||
|
|
f25de891bf | ||
|
|
e785533858 | ||
|
|
0699bc4147 | ||
|
|
d07a8258e9 | ||
|
|
d490e65315 | ||
|
|
7a632c9561 | ||
|
|
ec4b35fa5f | ||
|
|
b263755629 | ||
|
|
828585a260 | ||
|
|
3b33121dbc | ||
|
|
21484c8184 | ||
|
|
020385c850 | ||
|
|
c585f3d8ae | ||
|
|
3356e6c6cf | ||
|
|
2fa6ad22a3 | ||
|
|
b887519f26 | ||
|
|
b1e54a834c | ||
|
|
171e62a272 | ||
|
|
053770c117 | ||
|
|
911f3e1f54 | ||
|
|
1494ba95b3 | ||
|
|
c0b00ce146 | ||
|
|
47a08b829e | ||
|
|
953bb60aac | ||
|
|
fc4cb31277 | ||
|
|
9876636dbc | ||
|
|
8314ab03bd | ||
|
|
9d887ba3e7 | ||
|
|
1612a8a960 | ||
|
|
99af210e62 | ||
|
|
8fd108cde1 | ||
|
|
ca81d35936 | ||
|
|
d65aacac6f | ||
|
|
43f052a363 | ||
|
|
0e0a38ffa2 | ||
|
|
819586fc4e | ||
|
|
6b8a331428 | ||
|
|
57e7004e05 | ||
|
|
25c95cafe3 | ||
|
|
54be1a8918 | ||
|
|
d4a0573051 | ||
|
|
4a96eac67b | ||
|
|
2f4134a6d2 | ||
|
|
4f87dcc0c0 | ||
|
|
47eba6b720 | ||
|
|
cba537d561 | ||
|
|
c9d03cb042 | ||
|
|
e42c002fbd | ||
|
|
7c7b073a47 | ||
|
|
20090d21eb | ||
|
|
b0e69ff4b8 | ||
|
|
552bb0a98b | ||
|
|
19510858af | ||
|
|
44b2f6ee63 | ||
|
|
c5d3002f31 | ||
|
|
2e3659d4ee | ||
|
|
f8a5509a91 | ||
|
|
421f436871 | ||
|
|
a37c9d6cea | ||
|
|
d14d576d99 | ||
|
|
9391e44e4b | ||
|
|
a7aebe3a61 | ||
|
|
1fca9021a4 | ||
|
|
a39194b2ad | ||
|
|
bff93d9352 | ||
|
|
d76c9cd16d | ||
|
|
14774fe235 | ||
|
|
39046632aa | ||
|
|
fbb2afdb49 | ||
|
|
aff0402f71 | ||
|
|
cee9058c77 | ||
|
|
3f922b4c90 | ||
|
|
02d2d31cf3 | ||
|
|
240cf6a0ed | ||
|
|
dcd9ee93de | ||
|
|
2a8cd74ab1 | ||
|
|
63bc7055c2 | ||
|
|
1cca9733d6 | ||
|
|
1104da5e2c | ||
|
|
3a9718c09d | ||
|
|
55362c5573 | ||
|
|
0bba2299b3 | ||
|
|
45685af9e9 | ||
|
|
6c416a9338 | ||
|
|
1b0027e1d2 | ||
|
|
2409adf516 | ||
|
|
554801dfe4 | ||
|
|
20c23917e9 | ||
|
|
ef953b7574 | ||
|
|
6b79795229 | ||
|
|
9cb7ec2348 | ||
|
|
437c981d30 | ||
|
|
0334cae4c8 | ||
|
|
24c405d747 | ||
|
|
a3f5962809 | ||
|
|
0deb7495f0 | ||
|
|
d34f89fc4b | ||
|
|
a909ed498f | ||
|
|
16f4e17e8f | ||
|
|
0e9592a96c | ||
|
|
704ee6a53a | ||
|
|
5b9afbce9a | ||
|
|
396cc8e8ef | ||
|
|
bf776b5c06 | ||
|
|
be319f88d3 | ||
|
|
af40d555d4 | ||
|
|
f802dbe686 | ||
|
|
2379e3d83b | ||
|
|
9e90ac0412 | ||
|
|
c27948ca3c | ||
|
|
c3b9d664df | ||
|
|
31ef0a5223 | ||
|
|
14c58acea1 | ||
|
|
5dae20603e | ||
|
|
3f6fa94289 | ||
|
|
117615a8b0 | ||
|
|
4a52773c7d | ||
|
|
edfee495c6 | ||
|
|
7d112df7c6 | ||
|
|
9acaaade45 | ||
|
|
aaca28dbf6 | ||
|
|
d4ef5f9d4d | ||
|
|
2095dea801 | ||
|
|
a36f7ef10d | ||
|
|
9874962ee3 | ||
|
|
4b08022075 | ||
|
|
dc3db3aec4 | ||
|
|
0568c2a93d | ||
|
|
7ab0a6fc9e | ||
|
|
d6b780762e | ||
|
|
5ef66b5cf6 | ||
|
|
19e8cd5e48 | ||
|
|
df5117892f | ||
|
|
aaa4216f55 | ||
|
|
85ee5084b6 | ||
|
|
bb9ce117de | ||
|
|
00c5aa26bb | ||
|
|
bae4de227c | ||
|
|
253f891c5a | ||
|
|
6966159062 | ||
|
|
07d3b80c3e | ||
|
|
a41d0f3214 | ||
|
|
1ee15de78b | ||
|
|
b044358970 | ||
|
|
d2e11bb3bb | ||
|
|
a55bac899c | ||
|
|
c2380fb8df | ||
|
|
f31c644b13 | ||
|
|
26cd621d0e | ||
|
|
4c58512c54 | ||
|
|
04c1b47660 | ||
|
|
7b249e9fa6 | ||
|
|
46593ef68f | ||
|
|
b70f73c7d6 | ||
|
|
ece5e34fa2 | ||
|
|
74b400288d | ||
|
|
83f19b0631 | ||
|
|
c905d2d6fb | ||
|
|
d0c1eb2f04 | ||
|
|
d7d9d29c1d | ||
|
|
2a3f019ec6 | ||
|
|
d384d50b0d | ||
|
|
be8cb12bba | ||
|
|
a1aa2918be | ||
|
|
d7536bccb3 | ||
|
|
6b677355e1 | ||
|
|
85d625f6ac | ||
|
|
f5d6f87afe | ||
|
|
31d755f407 | ||
|
|
ebfc20d4b4 |
@@ -110,7 +110,7 @@
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/quotient-im/libQuotient.git",
|
||||
"branch": "0.8.x",
|
||||
"branch": "dev",
|
||||
"disable-submodules": true
|
||||
}
|
||||
],
|
||||
|
||||
55
.reuse/dep5
55
.reuse/dep5
@@ -1,55 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: NeoChat
|
||||
Upstream-Contact: Carl Schwan <carlschwan@kde.org>
|
||||
|
||||
Files: 128-logo.png icons/* logo.png org.kde.neochat.svg org.kde.neochat.tray.svg android/res/drawable/neochat.png
|
||||
Copyright: 2020 Carson Black <uhhadd@gmail.com>
|
||||
License: LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
|
||||
Files: android/res/drawable/splash.xml
|
||||
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: .gitignore
|
||||
Copyright: None
|
||||
License: CC0-1.0
|
||||
|
||||
Files: .gitlab/issue_templates/bug.md
|
||||
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: src/res.qrc src/res_android.qrc src/res_desktop.qrc
|
||||
Copyright: None
|
||||
License: CC0-1.0
|
||||
|
||||
Files: cmake/Flatpak/99-noto-mono-color-emoji.conf
|
||||
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: src/neochatconfig.kcfg
|
||||
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: src/neochat.notifyrc
|
||||
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
|
||||
License: BSD-2-Clause
|
||||
|
||||
Files: src/qml/confetti.png src/qml/glowdot.png
|
||||
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
|
||||
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
|
||||
|
||||
Files: appiumtests/data/*
|
||||
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
License: CC0-1.0
|
||||
|
||||
Files: src/purpose/purposeplugin.json
|
||||
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
License: BSD-2-Clause
|
||||
@@ -7,8 +7,8 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "25")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "03")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
@@ -107,7 +107,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
||||
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(QuotientQt6 0.8.2)
|
||||
find_package(QuotientQt6 0.9)
|
||||
set_package_properties(QuotientQt6 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
DESCRIPTION "Qt wrapper around Matrix API"
|
||||
@@ -115,11 +115,6 @@ set_package_properties(QuotientQt6 PROPERTIES
|
||||
PURPOSE "Talk with matrix server"
|
||||
)
|
||||
|
||||
if (NOT TARGET Olm::Olm)
|
||||
message(FATAL_ERROR "NeoChat requires Quotient with the E2EE feature enabled")
|
||||
endif()
|
||||
|
||||
|
||||
find_package(cmark)
|
||||
set_package_properties(cmark PROPERTIES
|
||||
TYPE REQUIRED
|
||||
|
||||
@@ -11,7 +11,7 @@ A Qt/QML based Matrix client.
|
||||
|
||||
<a href='https://matrix.org'><img src='https://matrix.org/docs/legacy/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
|
||||
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
|
||||
<a href='https://snapcraft.io/neochat'><img width='190px' alt='Download on the Snap Store' src='https://snapcraft.io/static/images/badges/en/snap-store-black.svg'/></a>
|
||||
<a href='https://snapcraft.io/neochat'><img width='190px' alt='Download on the Snap Store' src='https://apps.kde.org/store_badges/snapstore/en.svg'/></a>
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
84
REUSE.toml
Normal file
84
REUSE.toml
Normal file
@@ -0,0 +1,84 @@
|
||||
# SPDX-FileCopyrightText: none
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
version = 1
|
||||
SPDX-PackageName = "NeoChat"
|
||||
SPDX-PackageSupplier = "Carl Schwan <carlschwan@kde.org>"
|
||||
|
||||
[[annotations]]
|
||||
path = ["128-logo.png", "icons/**", "logo.png", "org.kde.neochat.svg", "org.kde.neochat.tray.svg", "android/res/drawable/neochat.png", "android/neochat-playstore.png"]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2020 Carson Black <uhhadd@gmail.com>"
|
||||
SPDX-License-Identifier = "LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL"
|
||||
|
||||
[[annotations]]
|
||||
path = "android/res/drawable/splash.xml"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
|
||||
[[annotations]]
|
||||
path = ".gitignore"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "None"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = ".gitlab/issue_templates/bug.md"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2021 Carl Schwan <carlschwan@kde.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = ["src/res.qrc", "src/res_android.qrc", "src/res_desktop.qrc"]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "None"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "cmake/Flatpak/99-noto-mono-color-emoji.conf"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2021 Carl Schwan <carlschwan@kde.org>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
|
||||
[[annotations]]
|
||||
path = "src/neochatconfig.kcfg"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
|
||||
[[annotations]]
|
||||
path = "src/neochat.notifyrc"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
|
||||
[[annotations]]
|
||||
path = ["src/qml/confetti.png", "src/qml/glowdot.png"]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2021 Alexey Andreyev <aa13q@ya.ru>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = ".flatpak-manifest.json"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2020-2022 Tobias Fella <tobias.fella@kde.org>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
|
||||
[[annotations]]
|
||||
path = "autotests/data/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "none"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "appiumtests/data/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>"
|
||||
SPDX-License-Identifier = "CC0-1.0"
|
||||
|
||||
[[annotations]]
|
||||
path = "src/purpose/purposeplugin.json"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
BIN
android/neochat-playstore.png
Normal file
BIN
android/neochat-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -5,6 +5,26 @@
|
||||
"!room_id_1234:localhost:1234": {
|
||||
"state": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"m.federate": true,
|
||||
"predecessor": {
|
||||
"event_id": "$something:example.org",
|
||||
"room_id": "!oldroom:example.org"
|
||||
},
|
||||
"room_version": "11"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "",
|
||||
"type": "m.room.create",
|
||||
"unsigned": {
|
||||
"age": 1234,
|
||||
"membership": "join"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "m.room.member",
|
||||
"state_key": "@user:localhost:1234",
|
||||
@@ -26,6 +46,26 @@
|
||||
},
|
||||
"timeline": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"m.federate": true,
|
||||
"predecessor": {
|
||||
"event_id": "$something:example.org",
|
||||
"room_id": "!oldroom:example.org"
|
||||
},
|
||||
"room_version": "11"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "",
|
||||
"type": "m.room.create",
|
||||
"unsigned": {
|
||||
"age": 1234,
|
||||
"membership": "join"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "m.room.message",
|
||||
"sender": "@user:localhost:1234",
|
||||
|
||||
@@ -83,6 +83,15 @@ def create_room():
|
||||
next_sync_payload = "sync_response_new_room"
|
||||
return response
|
||||
|
||||
@app.route("/_matrix/client/v3/publicRooms", methods=["POST"])
|
||||
def public_rooms():
|
||||
if request.get_json()["filter"]["generic_search_term"] == "forbidden":
|
||||
data = dict()
|
||||
data["errcode"] = "M_FORBIDDEN"
|
||||
data["error"] = "You are not allowed to search for this. Go to https://wikipedia.org for more information"
|
||||
return data, 403
|
||||
return dict()
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -53,12 +53,6 @@ ecm_add_test(
|
||||
TEST_NAME messageeventmodeltest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
actionshandlertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME actionshandlertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
windowcontrollertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
|
||||
@@ -1,41 +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 <QTest>
|
||||
|
||||
#include "actionshandler.h"
|
||||
#include "chatbarcache.h"
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
class ActionsHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Quotient::Connection *connection = Quotient::Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
|
||||
private Q_SLOTS:
|
||||
void nullObject();
|
||||
};
|
||||
|
||||
void ActionsHandlerTest::nullObject()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(nullptr, nullptr);
|
||||
|
||||
auto chatBarCache = new ChatBarCache(this);
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(nullptr, chatBarCache);
|
||||
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(room, nullptr);
|
||||
|
||||
// The final one should throw no warning so we make sure.
|
||||
QTest::failOnWarning("ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(room, chatBarCache);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(ActionsHandlerTest)
|
||||
#include "actionshandlertest.moc"
|
||||
@@ -32,8 +32,6 @@ private:
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void eventId();
|
||||
void nullEventId();
|
||||
void authorDisplayName();
|
||||
void nullAuthorDisplayName();
|
||||
void singleLineSidplayName();
|
||||
@@ -56,14 +54,8 @@ private Q_SLOTS:
|
||||
void nullSubtitle();
|
||||
void mediaInfo();
|
||||
void nullMediaInfo();
|
||||
void hasReply();
|
||||
void nullHasReply();
|
||||
void replyId();
|
||||
void nullReplyId();
|
||||
void replyAuthor();
|
||||
void nullReplyAuthor();
|
||||
void thread();
|
||||
void nullThread();
|
||||
void location();
|
||||
void nullLocation();
|
||||
};
|
||||
@@ -74,17 +66,6 @@ void EventHandlerTest::initTestCase()
|
||||
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-eventhandler-sync.json"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::eventId()
|
||||
{
|
||||
QCOMPARE(EventHandler::id(room->messageEvents().at(0).get()), QStringLiteral("$153456789:example.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullEventId()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "id called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::id(nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::authorDisplayName()
|
||||
{
|
||||
QCOMPARE(EventHandler::authorDisplayName(room, room->messageEvents().at(1).get()), QStringLiteral("before"));
|
||||
@@ -118,8 +99,9 @@ void EventHandlerTest::time()
|
||||
{
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
|
||||
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
|
||||
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
|
||||
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
|
||||
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, QTimeZone(QTimeZone::UTC))),
|
||||
QDateTime::fromMSecsSinceEpoch(1234, QTimeZone(QTimeZone::UTC)));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullTime()
|
||||
@@ -138,19 +120,19 @@ void EventHandlerTest::timeString()
|
||||
KFormat format;
|
||||
|
||||
QCOMPARE(EventHandler::timeString(event, false),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, true),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::LongFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::LongFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, QStringLiteral("hh:mm")),
|
||||
QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toString(QStringLiteral("hh:mm")));
|
||||
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toString(QStringLiteral("hh:mm")));
|
||||
}
|
||||
|
||||
void EventHandlerTest::highlighted()
|
||||
@@ -295,30 +277,6 @@ void EventHandlerTest::nullMediaInfo()
|
||||
QCOMPARE(EventHandler::mediaInfo(room, nullptr), QVariantMap());
|
||||
}
|
||||
|
||||
void EventHandlerTest::hasReply()
|
||||
{
|
||||
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(5).get()), true);
|
||||
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(0).get()), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHasReply()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "hasReply called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::hasReply(nullptr), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyId()
|
||||
{
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(5).get()), QStringLiteral("$153456789:example.org"));
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(0).get()), QStringLiteral(""));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyId()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "replyId called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::replyId(nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyAuthor()
|
||||
{
|
||||
auto replyEvent = room->messageEvents().at(0).get();
|
||||
@@ -344,29 +302,6 @@ void EventHandlerTest::nullReplyAuthor()
|
||||
QCOMPARE(EventHandler::replyAuthor(room, nullptr), RoomMember());
|
||||
}
|
||||
|
||||
void EventHandlerTest::thread()
|
||||
{
|
||||
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(0).get()), false);
|
||||
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(0).get()), QString());
|
||||
|
||||
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(9).get()), true);
|
||||
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(9).get()), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(9).get()), QStringLiteral("$threadroot:example.org"));
|
||||
|
||||
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(10).get()), true);
|
||||
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(10).get()), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(10).get()), QStringLiteral("$threadmessage1:example.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullThread()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::isThreaded(nullptr), false);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::threadRoot(nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::location()
|
||||
{
|
||||
QCOMPARE(EventHandler::latitude(room->messageEvents().at(7).get()), QStringLiteral("51.7035").toFloat());
|
||||
|
||||
@@ -535,7 +535,7 @@ void TextHandlerTest::componentOutput_data()
|
||||
QVariantMap{{QStringLiteral("class"), QStringLiteral("html")}}}};
|
||||
QTest::newRow("quote") << QStringLiteral("<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>")
|
||||
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
|
||||
MessageComponent{MessageComponentType::Quote, QStringLiteral("\"blockquote\""), {}}};
|
||||
MessageComponent{MessageComponentType::Quote, QStringLiteral("“blockquote”"), {}}};
|
||||
QTest::newRow("no tag first paragraph") << QStringLiteral("Text\n<p>Text</p>")
|
||||
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}};
|
||||
|
||||
@@ -54,16 +54,24 @@
|
||||
<summary xml:lang="ar">دردش على ماتركس</summary>
|
||||
<summary xml:lang="ca">Xat a Matrix</summary>
|
||||
<summary xml:lang="ca-valencia">Xat a Matrix</summary>
|
||||
<summary xml:lang="de">Über Matrix unterhalten</summary>
|
||||
<summary xml:lang="en-GB">Chat on Matrix</summary>
|
||||
<summary xml:lang="es">Charle en Matrix</summary>
|
||||
<summary xml:lang="eu">Berriketa Matrix-en</summary>
|
||||
<summary xml:lang="fi">Keskustelu Matrixissä</summary>
|
||||
<summary xml:lang="fr">Discuter sur Matrix</summary>
|
||||
<summary xml:lang="gl">Charlar en Matrix</summary>
|
||||
<summary xml:lang="he">התכתבות דרך Matrix</summary>
|
||||
<summary xml:lang="hu">Csevegés Matrixon</summary>
|
||||
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
|
||||
<summary xml:lang="it">Chat su Matrix</summary>
|
||||
<summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary>
|
||||
<summary xml:lang="lv">Tērzējiet „Matrix“ tīklā</summary>
|
||||
<summary xml:lang="nl">Chat op Matrix</summary>
|
||||
<summary xml:lang="nn">Prat med via Matrix</summary>
|
||||
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
|
||||
<summary xml:lang="sl">Klepet na Matrixu</summary>
|
||||
<summary xml:lang="sv">Chatta på Matrix</summary>
|
||||
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
|
||||
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
|
||||
<summary xml:lang="uk">Спілкування у Matrix</summary>
|
||||
@@ -285,6 +293,7 @@
|
||||
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
|
||||
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
|
||||
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
|
||||
<value key="KDE::supporters">Tanguy Fardet;[dabe](https://freeradical.zone/@dabe);[lengau](https://mastodon.world/@lengau);Joshua Strobl;Stuart Turton</value>
|
||||
</custom>
|
||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||
<screenshots>
|
||||
@@ -439,6 +448,7 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="24.12.0" date="2024-12-12"/>
|
||||
<release version="24.08.3" date="2024-11-07"/>
|
||||
<release version="24.08.2" date="2024-10-10"/>
|
||||
<release version="24.08.1" date="2024-09-12"/>
|
||||
|
||||
@@ -87,47 +87,32 @@ GenericName[uk]=Клієнт Matrix
|
||||
GenericName[x-test]=xxMatrix Clientxx
|
||||
GenericName[zh_CN]=Matrix 客户端
|
||||
GenericName[zh_TW]=Matrix 用戶端
|
||||
Comment=Client for the Matrix protocol
|
||||
Comment[ar]=عميل لميفاق ماتركس
|
||||
Comment[az]=Matrix protokolu üçün müştəri
|
||||
Comment[ca]=Client per al protocol Matrix
|
||||
Comment[ca@valencia]=Client per al protocol Matrix
|
||||
Comment[de]=Programm für das Matrix-Protokoll
|
||||
Comment[el]=Πελάτης για το πρωτόκολλο Matrix
|
||||
Comment[en_GB]=Client for the Matrix protocol
|
||||
Comment[eo]=Kliento por la Matrix-protokolo
|
||||
Comment[es]=Cliente para el protocolo Matrix
|
||||
Comment[eu]=Matrix protokolorako bezeroa
|
||||
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
||||
Comment[fr]=Client pour le protocole « Matrix »
|
||||
Comment[gl]=Cliente para o protocolo Matrix.
|
||||
Comment[he]=לקוח לפרוטוקול Matrix
|
||||
Comment[hu]=Kliens a Matrix protokollhoz
|
||||
Comment[ia]=Cliente per le protocollo de Matrix
|
||||
Comment[id]=Klien untuk protokol Matrix
|
||||
Comment[ie]=Un cliente del protocol Matrix
|
||||
Comment[it]=Client per il protocollo Matrix
|
||||
Comment[ka]=კლიენტი Matrix-ის პროტოკოლისთვის
|
||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||
Comment[lt]=Matrix protokolo kliento programa
|
||||
Comment[lv]=Klients „Matrix“ protokolam
|
||||
Comment[nl]=Client voor het Matrix-protocol
|
||||
Comment[nn]=Klient for Matrix-protokollen
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
Comment[pl]=Program obsługi protokołu Matriksa
|
||||
Comment[pt]=Cliente para o protocolo Matrix
|
||||
Comment[pt_BR]=Cliente para o protocolo Matrix
|
||||
Comment[ro]=Client pentru protocolul Matrix
|
||||
Comment[ru]=Клиент для протокола Matrix
|
||||
Comment[sk]=Klient protokolu Matrix
|
||||
Comment[sl]=Odjemalec za protokol Matrix
|
||||
Comment[sv]=Klient för protokollet Matrix
|
||||
Comment[ta]=Matrix நெறிமுறைக்கான வாங்கி
|
||||
Comment[tr]=Matrix protokolü için istemci
|
||||
Comment[uk]=Клієнт протоколу Matrix
|
||||
Comment[x-test]=xxClient for the Matrix protocolxx
|
||||
Comment[zh_CN]=为 Matrix 协议打造的客户端
|
||||
Comment[zh_TW]=Matrix 通訊協定的用戶端
|
||||
Comment=Chat on Matrix
|
||||
Comment[ar]=دردش على ماتركس
|
||||
Comment[ca]=Xat a Matrix
|
||||
Comment[ca@valencia]=Xat a Matrix
|
||||
Comment[de]=Über Matrix unterhalten
|
||||
Comment[en_GB]=Chat on Matrix
|
||||
Comment[es]=Chat en Matrix
|
||||
Comment[eu]=Berriketa Matrix-en
|
||||
Comment[fi]=Keskustele Matrixissä
|
||||
Comment[fr]=Clavarder sur Matrix
|
||||
Comment[gl]=Charle en Matrix
|
||||
Comment[he]=התכתבות דרך Matrix
|
||||
Comment[hu]=Csevegés Matrixon
|
||||
Comment[ia]=Conversation en ditecto sur Matrix
|
||||
Comment[it]= su Matrix
|
||||
Comment[ka]=ჩატი Matrix-ზე
|
||||
Comment[lv]=Tērzējiet „Matrix“ tīklā
|
||||
Comment[nl]=Chat op Matrix
|
||||
Comment[pl]=Rozmawiaj na Matriksie
|
||||
Comment[sl]=Klepet na Matrixu
|
||||
Comment[sv]=Chatta på Matrix
|
||||
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
|
||||
Comment[tr]=Matrix Üzerinde Sohbet Et
|
||||
Comment[uk]=Спілкування у Matrix
|
||||
Comment[x-test]=xxChat on Matrixxx
|
||||
Comment[zh_TW]=在 Matrix 上聊天
|
||||
MimeType=x-scheme-handler/matrix;
|
||||
Exec=neochat %u
|
||||
Terminal=false
|
||||
|
||||
1391
po/ar/neochat.po
1391
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1290
po/ast/neochat.po
1290
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
1465
po/az/neochat.po
1465
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1368
po/ca/neochat.po
1368
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1526
po/cs/neochat.po
1526
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1444
po/da/neochat.po
1444
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1813
po/de/neochat.po
1813
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1486
po/el/neochat.po
1486
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1525
po/en_GB/neochat.po
1525
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1545
po/eo/neochat.po
1545
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1370
po/es/neochat.po
1370
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1456
po/eu/neochat.po
1456
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
2740
po/fi/neochat.po
2740
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1392
po/fr/neochat.po
1392
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1433
po/gl/neochat.po
1433
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
1596
po/hu/neochat.po
1596
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1520
po/ia/neochat.po
1520
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1500
po/id/neochat.po
1500
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1450
po/ie/neochat.po
1450
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1524
po/it/neochat.po
1524
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1290
po/ja/neochat.po
1290
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1367
po/ka/neochat.po
1367
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1522
po/ko/neochat.po
1522
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1290
po/lt/neochat.po
1290
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1522
po/lv/neochat.po
1522
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1374
po/nl/neochat.po
1374
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1522
po/nn/neochat.po
1522
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1442
po/pa/neochat.po
1442
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1546
po/pl/neochat.po
1546
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1500
po/pt/neochat.po
1500
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1468
po/pt_BR/neochat.po
1468
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1616
po/ru/neochat.po
1616
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
2032
po/sk/neochat.po
2032
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1370
po/sl/neochat.po
1370
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1390
po/sv/neochat.po
1390
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1370
po/ta/neochat.po
1370
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1416
po/tok/neochat.po
1416
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1570
po/tr/neochat.po
1570
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1374
po/uk/neochat.po
1374
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1346
po/zh_CN/neochat.po
1346
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1520
po/zh_TW/neochat.po
1520
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -92,7 +92,7 @@ parts:
|
||||
- olm
|
||||
- qtkeychain
|
||||
source: https://github.com/quotient-im/libQuotient.git
|
||||
source-tag: 0.8.2
|
||||
source-tag: 0.9.1
|
||||
source-depth: 1
|
||||
plugin: cmake
|
||||
build-packages:
|
||||
|
||||
@@ -10,8 +10,6 @@ endif()
|
||||
add_library(neochat STATIC
|
||||
controller.cpp
|
||||
controller.h
|
||||
actionshandler.cpp
|
||||
actionshandler.h
|
||||
models/emojimodel.cpp
|
||||
models/emojimodel.h
|
||||
emojitones.cpp
|
||||
@@ -196,12 +194,20 @@ add_library(neochat STATIC
|
||||
models/threadmodel.h
|
||||
enums/messagetype.h
|
||||
messagecomponent.h
|
||||
enums/roomsortparameter.cpp
|
||||
enums/roomsortparameter.h
|
||||
)
|
||||
|
||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||
QT_QML_SINGLETON_TYPE TRUE
|
||||
)
|
||||
|
||||
if(ANDROID OR WIN32)
|
||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||
QT_QML_SOURCE_TYPENAME ShareAction
|
||||
)
|
||||
endif()
|
||||
|
||||
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
|
||||
QML_FILES
|
||||
@@ -313,13 +319,9 @@ if(NOT ANDROID AND NOT WIN32)
|
||||
qml/EditMenu.qml
|
||||
)
|
||||
else()
|
||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||
QT_RESOURCE_ALIAS qml/ShareAction.qml
|
||||
)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
|
||||
endif()
|
||||
|
||||
|
||||
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
|
||||
|
||||
if(WIN32)
|
||||
@@ -533,6 +535,7 @@ if(ANDROID)
|
||||
"kde"
|
||||
"list-remove-symbolic"
|
||||
"edit-delete"
|
||||
"user-home-symbolic"
|
||||
)
|
||||
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
|
||||
else()
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "actionshandler.h"
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
void ActionsHandler::handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache)
|
||||
{
|
||||
if (room == nullptr || chatBarCache == nullptr) {
|
||||
qWarning() << "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chatBarCache->attachmentPath().isEmpty()) {
|
||||
QUrl url(chatBarCache->attachmentPath());
|
||||
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||
room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
|
||||
chatBarCache->setAttachmentPath({});
|
||||
chatBarCache->setText({});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto handledText = handleMentions(chatBarCache);
|
||||
const auto result = handleQuickEdit(room, handledText);
|
||||
if (!result) {
|
||||
handleMessage(room, handledText, chatBarCache);
|
||||
}
|
||||
}
|
||||
|
||||
QString ActionsHandler::handleMentions(ChatBarCache *chatBarCache)
|
||||
{
|
||||
const auto mentions = chatBarCache->mentions();
|
||||
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
|
||||
return a.cursor.anchor() > b.cursor.anchor();
|
||||
});
|
||||
|
||||
auto handledText = chatBarCache->text();
|
||||
for (const auto &mention : *mentions) {
|
||||
if (mention.text.isEmpty() || mention.id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
handledText = handledText.replace(mention.cursor.anchor(),
|
||||
mention.cursor.position() - mention.cursor.anchor(),
|
||||
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
|
||||
}
|
||||
mentions->clear();
|
||||
|
||||
return handledText;
|
||||
}
|
||||
|
||||
bool ActionsHandler::handleQuickEdit(NeoChatRoom *room, const QString &handledText)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
|
||||
auto match = sed.match(handledText);
|
||||
if (match.hasMatch()) {
|
||||
const QString regex = match.captured(1);
|
||||
const QString replacement = match.captured(2).toHtmlEscaped();
|
||||
const QString flags = match.captured(3);
|
||||
|
||||
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
|
||||
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event->senderId() == room->localMember().id() && event->has<EventContent::TextContent>()) {
|
||||
#else
|
||||
if (event->senderId() == room->localMember().id() && event->hasTextContent()) {
|
||||
#endif
|
||||
QString originalString;
|
||||
if (event->content()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
originalString = event->get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
|
||||
#endif
|
||||
} else {
|
||||
originalString = event->plainBody();
|
||||
}
|
||||
if (flags == "/g"_L1) {
|
||||
room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
|
||||
} else {
|
||||
room->postHtmlMessage(handledText,
|
||||
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
||||
event->msgtype(),
|
||||
{},
|
||||
event->id());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActionsHandler::handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto messageType = RoomMessageEvent::MsgType::Text;
|
||||
|
||||
if (handledText.startsWith(QLatin1Char('/'))) {
|
||||
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(), room, chatBarCache);
|
||||
if (action.messageType.has_value()) {
|
||||
messageType = *action.messageType;
|
||||
}
|
||||
if (action.messageAction) {
|
||||
break;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextHandler textHandler;
|
||||
textHandler.setData(handledText);
|
||||
handledText = textHandler.handleSendText();
|
||||
|
||||
if (handledText.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
room->postMessage(chatBarCache->text(), handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
|
||||
}
|
||||
|
||||
#include "moc_actionshandler.cpp"
|
||||
@@ -1,43 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
class ChatBarCache;
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ActionsHandler
|
||||
*
|
||||
* This class contains functions to handle chat messages ready for posting to a room.
|
||||
*
|
||||
* Everything that needs to be done to prepare the message for posting in a room
|
||||
* including:
|
||||
* - File handling
|
||||
* - User mentions
|
||||
* - Quick edits
|
||||
* - Chat actions
|
||||
* - Custom emojis
|
||||
*
|
||||
* @note A chat action is a message starting with /, resulting in something other
|
||||
* than a normal message being sent (e.g. /me, /join).
|
||||
*
|
||||
* @sa ActionsModel, NeoChatRoom
|
||||
*/
|
||||
class ActionsHandler
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Pre-process text and send message event.
|
||||
*/
|
||||
static void handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache);
|
||||
|
||||
private:
|
||||
static QString handleMentions(ChatBarCache *chatBarCache);
|
||||
static bool handleQuickEdit(NeoChatRoom *room, const QString &handledText);
|
||||
|
||||
static void handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache);
|
||||
};
|
||||
@@ -176,13 +176,14 @@ QQC2.Control {
|
||||
RowLayout {
|
||||
QQC2.ScrollView {
|
||||
id: chatBarScrollView
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 8
|
||||
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
|
||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 3
|
||||
|
||||
// HACK: This is to stop the ScrollBar flickering on and off as the height is increased
|
||||
QQC2.ScrollBar.vertical.policy: chatBarHeightAnimation.running && implicitHeight <= height ? QQC2.ScrollBar.AlwaysOff : QQC2.ScrollBar.AsNeeded
|
||||
@@ -251,20 +252,22 @@ QQC2.Control {
|
||||
}
|
||||
}
|
||||
Keys.onEnterPressed: event => {
|
||||
const controlIsPressed = event.modifiers & Qt.ControlModifier;
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.complete();
|
||||
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile) {
|
||||
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile || NeoChatConfig.sendMessageWith === 1 && !controlIsPressed || NeoChatConfig.sendMessageWith === 0 && controlIsPressed) {
|
||||
textField.insert(cursorPosition, "\n");
|
||||
} else {
|
||||
} else if (NeoChatConfig.sendMessageWith === 0 && !controlIsPressed || NeoChatConfig.sendMessageWith === 1 && controlIsPressed) {
|
||||
_private.postMessage();
|
||||
}
|
||||
}
|
||||
Keys.onReturnPressed: event => {
|
||||
const controlIsPressed = event.modifiers & Qt.ControlModifier;
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.complete();
|
||||
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile) {
|
||||
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile || NeoChatConfig.sendMessageWith === 1 && !controlIsPressed || NeoChatConfig.sendMessageWith === 0 && controlIsPressed) {
|
||||
textField.insert(cursorPosition, "\n");
|
||||
} else {
|
||||
} else if (NeoChatConfig.sendMessageWith === 0 && !controlIsPressed || NeoChatConfig.sendMessageWith === 1 && controlIsPressed) {
|
||||
_private.postMessage();
|
||||
}
|
||||
}
|
||||
@@ -318,12 +321,11 @@ QQC2.Control {
|
||||
id: actionsRow
|
||||
spacing: 0
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing * 1.5
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing * 4
|
||||
|
||||
Repeater {
|
||||
model: root.actions
|
||||
delegate: QQC2.ToolButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
|
||||
onClicked: modelData.trigger()
|
||||
|
||||
@@ -340,7 +342,6 @@ QQC2.Control {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateSizeHelper {
|
||||
id: chatBarSizeHelper
|
||||
startBreakpoint: Kirigami.Units.gridUnit * 46
|
||||
@@ -405,7 +406,6 @@ QQC2.Control {
|
||||
repeatTimer.stop();
|
||||
root.currentRoom.markAllMessagesAsRead();
|
||||
textField.clear();
|
||||
_private.chatBarCache.clearRelations();
|
||||
messageSent();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ QQC2.ItemDelegate {
|
||||
anchors.fill: parent
|
||||
visible: root.emoji.startsWith("mxc") || root.isImage
|
||||
source: visible ? root.emoji : ""
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ QQC2.ScrollView {
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
icon.name: root.stickers ? "stickers" : "preferences-desktop-emoticons"
|
||||
text: root.stickers ? i18n("No stickers") : i18n("No emojis")
|
||||
visible: emojis.count === 0
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height
|
||||
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
|
||||
visible: categories.count !== 0
|
||||
|
||||
ListView {
|
||||
id: categories
|
||||
@@ -201,8 +202,13 @@ ColumnLayout {
|
||||
width: root.categoryIconSize
|
||||
height: width
|
||||
checked: stickerModel.packIndex === model.index
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
contentItem: Image {
|
||||
source: model.avatarUrl
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
QQC2.ToolTip.text: model.name
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
#include "actionshandler.h"
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "eventhandler.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
ChatBarCache::ChatBarCache(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -29,6 +32,37 @@ void ChatBarCache::setText(const QString &text)
|
||||
Q_EMIT textChanged();
|
||||
}
|
||||
|
||||
QString ChatBarCache::sendText() const
|
||||
{
|
||||
if (!attachmentPath().isEmpty()) {
|
||||
QUrl url(attachmentPath());
|
||||
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||
return text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : text();
|
||||
}
|
||||
|
||||
return formatMentions();
|
||||
}
|
||||
|
||||
QString ChatBarCache::formatMentions() const
|
||||
{
|
||||
auto mentions = m_mentions;
|
||||
std::sort(mentions.begin(), mentions.end(), [](const auto &a, const auto &b) {
|
||||
return a.cursor.anchor() > b.cursor.anchor();
|
||||
});
|
||||
|
||||
auto formattedText = text();
|
||||
for (const auto &mention : mentions) {
|
||||
if (mention.text.isEmpty() || mention.id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
formattedText = formattedText.replace(mention.cursor.anchor(),
|
||||
mention.cursor.position() - mention.cursor.anchor(),
|
||||
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
|
||||
}
|
||||
|
||||
return formattedText;
|
||||
}
|
||||
|
||||
bool ChatBarCache::isReplying() const
|
||||
{
|
||||
return m_relationType == Reply && !m_relationId.isEmpty();
|
||||
@@ -268,7 +302,53 @@ void ChatBarCache::postMessage()
|
||||
return;
|
||||
}
|
||||
|
||||
ActionsHandler::handleMessageEvent(room, this);
|
||||
if (!attachmentPath().isEmpty()) {
|
||||
room->uploadFile(QUrl(attachmentPath()), sendText());
|
||||
clearCache();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = ActionsModel::handleAction(room, this);
|
||||
if (!result.first.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextHandler textHandler;
|
||||
textHandler.setData(*std::get<std::optional<QString>>(result));
|
||||
const auto sendText = textHandler.handleSendText();
|
||||
|
||||
if (sendText.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool isReply = !replyId().isEmpty();
|
||||
const auto replyIt = room->findInTimeline(replyId());
|
||||
if (replyIt == room->historyEdge()) {
|
||||
isReply = false;
|
||||
}
|
||||
|
||||
auto content = std::make_unique<Quotient::EventContent::TextContent>(sendText, u"text/html"_s);
|
||||
std::optional<Quotient::EventRelation> relatesTo = std::nullopt;
|
||||
|
||||
if (!threadId().isEmpty()) {
|
||||
relatesTo = Quotient::EventRelation::replyInThread(threadId(), !isReply, isReply ? replyId() : threadId());
|
||||
} else if (!editId().isEmpty()) {
|
||||
relatesTo = Quotient::EventRelation::replace(editId());
|
||||
} else if (isReply) {
|
||||
relatesTo = Quotient::EventRelation::replyTo(replyId());
|
||||
}
|
||||
|
||||
const auto type = std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result);
|
||||
room->post<Quotient::RoomMessageEvent>(text(), type ? *type : Quotient::RoomMessageEvent::MsgType::Text, std::move(content), relatesTo);
|
||||
clearCache();
|
||||
}
|
||||
|
||||
void ChatBarCache::clearCache()
|
||||
{
|
||||
setText({});
|
||||
m_mentions.clear();
|
||||
m_savedText = QString();
|
||||
clearRelations();
|
||||
}
|
||||
|
||||
#include "moc_chatbarcache.cpp"
|
||||
|
||||
@@ -153,6 +153,7 @@ public:
|
||||
explicit ChatBarCache(QObject *parent = nullptr);
|
||||
|
||||
QString text() const;
|
||||
QString sendText() const;
|
||||
void setText(const QString &text);
|
||||
|
||||
bool isReplying() const;
|
||||
@@ -215,6 +216,8 @@ Q_SIGNALS:
|
||||
|
||||
private:
|
||||
QString m_text = QString();
|
||||
QString formatMentions() const;
|
||||
|
||||
QString m_relationId = QString();
|
||||
RelationType m_relationType = RelationType::None;
|
||||
QString m_threadId = QString();
|
||||
@@ -223,4 +226,6 @@ private:
|
||||
QString m_savedText;
|
||||
|
||||
QPointer<MessageContentModel> m_relationContentModel;
|
||||
|
||||
void clearCache();
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
ColorSchemer::ColorSchemer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, c(new KColorSchemeManager(this))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -18,17 +17,17 @@ ColorSchemer::~ColorSchemer()
|
||||
|
||||
QAbstractItemModel *ColorSchemer::model() const
|
||||
{
|
||||
return c->model();
|
||||
return KColorSchemeManager::instance()->model();
|
||||
}
|
||||
|
||||
void ColorSchemer::apply(int idx)
|
||||
{
|
||||
c->activateScheme(c->model()->index(idx, 0));
|
||||
KColorSchemeManager::instance()->activateScheme(KColorSchemeManager::instance()->model()->index(idx, 0));
|
||||
}
|
||||
|
||||
int ColorSchemer::indexForCurrentScheme()
|
||||
{
|
||||
return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||
return KColorSchemeManager::instance()->indexForSchemeId(KColorSchemeManager::instance()->activeSchemeId()).row();
|
||||
}
|
||||
|
||||
#include "moc_colorschemer.cpp"
|
||||
|
||||
@@ -49,7 +49,4 @@ public:
|
||||
* @sa KColorScheme
|
||||
*/
|
||||
Q_INVOKABLE int indexForCurrentScheme();
|
||||
|
||||
private:
|
||||
KColorSchemeManager *c;
|
||||
};
|
||||
|
||||
@@ -63,11 +63,7 @@ Controller::Controller(QObject *parent)
|
||||
});
|
||||
} else {
|
||||
auto c = new NeoChatConnection(this);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
c->assumeIdentity(QStringLiteral("@user:localhost:1234"), QStringLiteral("device_1234"), QStringLiteral("token_1234"));
|
||||
#else
|
||||
c->assumeIdentity(QStringLiteral("@user:localhost:1234"), QStringLiteral("token_1234"));
|
||||
#endif
|
||||
connect(c, &Connection::connected, this, [c, this]() {
|
||||
m_accountRegistry.add(c);
|
||||
c->syncLoop();
|
||||
@@ -230,11 +226,7 @@ void Controller::invokeLogin()
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
});
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection->assumeIdentity(account.userId(), account.deviceId(), accessToken);
|
||||
#else
|
||||
connection->assumeIdentity(account.userId(), accessToken);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -431,21 +423,21 @@ void Controller::setTestMode(bool test)
|
||||
|
||||
void Controller::removeConnection(const QString &userId)
|
||||
{
|
||||
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
|
||||
auto connection = m_connectionsLoading[userId];
|
||||
// When loadAccessTokenFromKeyChain() fails m_connectionsLoading won't have an
|
||||
// entry for it so we need to check both separately.
|
||||
if (m_accountsLoading.contains(userId)) {
|
||||
m_accountsLoading.removeAll(userId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
}
|
||||
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
|
||||
auto connection = m_connectionsLoading[userId];
|
||||
SettingsGroup("Accounts"_ls).remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::csSupported() const
|
||||
{
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::revertToDefaultConfig()
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
@@ -23,7 +24,7 @@ ColumnLayout {
|
||||
model: root.connection.accountDataEventTypes
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: modelData
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
sourceText: root.connection.accountDataJsonString(modelData)
|
||||
}, {
|
||||
title: i18nc("@title:window", "Event Source"),
|
||||
@@ -32,4 +33,28 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormSwitchDelegate {
|
||||
id: showAccessTokenCheckbox
|
||||
text: i18nc("@info", "Show Access Token")
|
||||
description: i18n("This should not be shared with anyone, even other users. This token gives full access to your account.")
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18nc("@info", "Access Token")
|
||||
description: root.connection.accessToken
|
||||
visible: showAccessTokenCheckbox.checked
|
||||
|
||||
contentItem.children: QQC2.Button {
|
||||
text: i18nc("@action:button", "Copy access token to clipboard")
|
||||
icon.name: "edit-copy"
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
onClicked: Clipboard.saveText(root.connection.accessToken)
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
@@ -20,19 +21,22 @@ ColumnLayout {
|
||||
title: i18nc("@title", "Choose Room")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormComboBoxDelegate {
|
||||
id: roomComboBox
|
||||
text: i18n("Room")
|
||||
textRole: "escapedDisplayName"
|
||||
valueRole: "roomId"
|
||||
displayText: RoomManager.roomListModel.data(RoomManager.roomListModel.index(currentIndex, 0), RoomListModel.EscapedDisplayNameRole)
|
||||
model: RoomManager.roomListModel
|
||||
currentIndex: 0
|
||||
displayMode: FormCard.FormComboBoxDelegate.Page
|
||||
Component.onCompleted: currentIndex = RoomManager.roomListModel.rowForRoom(root.room)
|
||||
onCurrentValueChanged: root.room = RoomManager.roomListModel.roomByAliasOrId(roomComboBox.currentValue)
|
||||
FormCard.FormButtonDelegate {
|
||||
text: root.room?.displayNameForHtml ?? i18nc("@info", "No room selected")
|
||||
description: i18nc("@info", "Click to choose a room");
|
||||
|
||||
onClicked: {
|
||||
let dialog = root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
||||
connection: root.connection,
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Choose Room"),
|
||||
width: Kirigami.Units.gridUnit * 24
|
||||
});
|
||||
dialog.chosen.connect(id => root.room = root.connection.room(id))
|
||||
}
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
visible: root.room
|
||||
text: i18n("Room Id: %1", root.room.id)
|
||||
}
|
||||
}
|
||||
@@ -47,7 +51,7 @@ ColumnLayout {
|
||||
model: root.room.accountDataEventTypes
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: modelData
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
sourceText: root.room.roomAcountDataJson(text)
|
||||
}, {
|
||||
title: i18n("Event Source"),
|
||||
@@ -77,7 +81,7 @@ ColumnLayout {
|
||||
if (model.eventCount === 1) {
|
||||
openEventSource(model.type, model.stateKey);
|
||||
} else {
|
||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
|
||||
root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
|
||||
room: root.room,
|
||||
eventType: model.type
|
||||
}, {
|
||||
@@ -89,7 +93,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
function openEventSource(type: string, stateKey: string): void {
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
model: stateModel,
|
||||
allowEdit: true,
|
||||
room: root.room,
|
||||
|
||||
@@ -37,7 +37,7 @@ FormCard.FormCardPage {
|
||||
}
|
||||
|
||||
function openEventSource(stateKey: string): void {
|
||||
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
model: stateKeysModel,
|
||||
allowEdit: true,
|
||||
room: root.room,
|
||||
|
||||
85
src/enums/roomsortparameter.cpp
Normal file
85
src/enums/roomsortparameter.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
// SPDX-FileCopyrightText: 2024 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 "roomsortparameter.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
template<typename T>
|
||||
int typeCompare(T left, T right)
|
||||
{
|
||||
return left == right ? 0 : left > right ? 1 : -1;
|
||||
}
|
||||
|
||||
template<>
|
||||
int typeCompare<QString>(QString left, QString right)
|
||||
{
|
||||
return left.localeAwareCompare(right);
|
||||
}
|
||||
}
|
||||
|
||||
int RoomSortParameter::compareParameter(Parameter parameter, NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
|
||||
{
|
||||
switch (parameter) {
|
||||
case AlphabeticalAscending:
|
||||
return compareParameter<AlphabeticalAscending>(leftRoom, rightRoom);
|
||||
case AlphabeticalDescending:
|
||||
return compareParameter<AlphabeticalDescending>(leftRoom, rightRoom);
|
||||
case HasUnread:
|
||||
return compareParameter<HasUnread>(leftRoom, rightRoom);
|
||||
case MostUnread:
|
||||
return compareParameter<MostUnread>(leftRoom, rightRoom);
|
||||
case HasHighlight:
|
||||
return compareParameter<HasHighlight>(leftRoom, rightRoom);
|
||||
case MostHighlights:
|
||||
return compareParameter<MostHighlights>(leftRoom, rightRoom);
|
||||
case LastActive:
|
||||
return compareParameter<LastActive>(leftRoom, rightRoom);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
|
||||
{
|
||||
return -typeCompare(leftRoom->displayName(), rightRoom->displayName());
|
||||
}
|
||||
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
|
||||
{
|
||||
return typeCompare(leftRoom->displayName(), rightRoom->displayName());
|
||||
}
|
||||
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
|
||||
{
|
||||
return typeCompare(leftRoom->contextAwareNotificationCount() > 0, rightRoom->contextAwareNotificationCount() > 0);
|
||||
}
|
||||
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
|
||||
{
|
||||
return typeCompare(leftRoom->contextAwareNotificationCount(), rightRoom->contextAwareNotificationCount());
|
||||
}
|
||||
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
|
||||
{
|
||||
const auto leftHighlight = leftRoom->highlightCount() > 0 && leftRoom->contextAwareNotificationCount() > 0;
|
||||
const auto rightHighlight = rightRoom->highlightCount() > 0 && rightRoom->contextAwareNotificationCount() > 0;
|
||||
return typeCompare(leftHighlight, rightHighlight);
|
||||
}
|
||||
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
|
||||
{
|
||||
return typeCompare(int(leftRoom->highlightCount()), int(rightRoom->highlightCount()));
|
||||
}
|
||||
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
|
||||
{
|
||||
return typeCompare(leftRoom->lastActiveTime(), rightRoom->lastActiveTime());
|
||||
}
|
||||
122
src/enums/roomsortparameter.h
Normal file
122
src/enums/roomsortparameter.h
Normal file
@@ -0,0 +1,122 @@
|
||||
// SPDX-FileCopyrightText: 2024 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 "neochatroom.h"
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
/**
|
||||
* @class RoomSortParameter
|
||||
*
|
||||
* A class with the Parameter enum for room sort parameters.
|
||||
*/
|
||||
class RoomSortParameter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the available sort parameters.
|
||||
*/
|
||||
enum Parameter {
|
||||
AlphabeticalAscending,
|
||||
AlphabeticalDescending,
|
||||
HasUnread,
|
||||
MostUnread,
|
||||
HasHighlight,
|
||||
MostHighlights,
|
||||
LastActive,
|
||||
};
|
||||
Q_ENUM(Parameter)
|
||||
|
||||
/**
|
||||
* @brief Translate the Parameter enum value to a human readable name string.
|
||||
*
|
||||
* @sa Parameter
|
||||
*/
|
||||
static QString parameterName(Parameter parameter)
|
||||
{
|
||||
switch (parameter) {
|
||||
case Parameter::AlphabeticalAscending:
|
||||
return i18nc("As in sorting alphabetically with A first and Z last", "Alphabetical Ascending");
|
||||
case Parameter::AlphabeticalDescending:
|
||||
return i18nc("As in sorting alphabetically with Z first and A last", "Alphabetical Descending");
|
||||
case Parameter::HasUnread:
|
||||
return i18nc("As in sorting rooms with unread message above those without", "Has Unread Messages");
|
||||
case Parameter::MostUnread:
|
||||
return i18nc("As in sorting rooms with the most unread messages higher", "Most Unread Messages");
|
||||
case Parameter::HasHighlight:
|
||||
return i18nc("As in sorting rooms with highlighted message above those without", "Has Highlighted Messages");
|
||||
case Parameter::MostHighlights:
|
||||
return i18nc("As in sorting rooms with the most highlighted messages higher", "Most Highlighted Messages");
|
||||
case Parameter::LastActive:
|
||||
return i18nc("As in sorting the chat room with the newest meassage first", "Last Active");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Translate the Parameter enum value to a human readable description string.
|
||||
*
|
||||
* @sa Parameter
|
||||
*/
|
||||
static QString parameterDescription(Parameter parameter)
|
||||
{
|
||||
switch (parameter) {
|
||||
case Parameter::AlphabeticalAscending:
|
||||
return i18nc("@info", "Room names closer to A alphabetically are higher");
|
||||
case Parameter::AlphabeticalDescending:
|
||||
return i18nc("@info", "Room names closer to Z alphabetically are higher");
|
||||
case Parameter::HasUnread:
|
||||
return i18nc("@info", "Rooms with unread messages are higher");
|
||||
case Parameter::MostUnread:
|
||||
return i18nc("@info", "Rooms with the most unread message are higher");
|
||||
case Parameter::HasHighlight:
|
||||
return i18nc("@info", "Rooms with highlighted messages are higher");
|
||||
case Parameter::MostHighlights:
|
||||
return i18nc("@info", "Rooms with the most highlighted messages are higher");
|
||||
case Parameter::LastActive:
|
||||
return i18nc("@info", "Rooms with the newer messages are higher");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Compare the given parameter of the two given rooms.
|
||||
*
|
||||
* @return 0 if they are equal, 1 if the left is greater and -1 if the right is greater.
|
||||
*
|
||||
* @sa Parameter
|
||||
*/
|
||||
static int compareParameter(Parameter parameter, NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
|
||||
|
||||
private:
|
||||
template<Parameter parameter>
|
||||
static int compareParameter(NeoChatRoom *, NeoChatRoom *)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
|
||||
template<>
|
||||
int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
|
||||
@@ -49,16 +49,6 @@ Q_DECLARE_FLAGS(MemberChanges, MemberChange)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(MemberChanges)
|
||||
};
|
||||
|
||||
QString EventHandler::id(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "id called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
return !event->id().isEmpty() ? event->id() : event->transactionId();
|
||||
}
|
||||
|
||||
QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
@@ -70,7 +60,7 @@ QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient:
|
||||
return {};
|
||||
}
|
||||
|
||||
if (is<RoomMemberEvent>(*event) && !event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].isNull()
|
||||
if (is<RoomMemberEvent>(*event) && event->unsignedJson()[QStringLiteral("prev_content")].toObject().contains("displayname"_L1)
|
||||
&& event->stateKey() == event->senderId()) {
|
||||
auto previousDisplayName = event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].toString().toHtmlEscaped();
|
||||
if (previousDisplayName.isEmpty()) {
|
||||
@@ -225,18 +215,10 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
{
|
||||
QString body;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (event.hasFileContent()) {
|
||||
#endif
|
||||
// if filename is given or body is equal to filename,
|
||||
// then body is a caption
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString filename = event.get<EventContent::FileContent>()->originalName;
|
||||
#else
|
||||
QString filename = event.content()->fileInfo()->originalName;
|
||||
#endif
|
||||
QString body = event.plainBody();
|
||||
if (filename.isEmpty() || filename == body) {
|
||||
return QString();
|
||||
@@ -244,13 +226,8 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
return body;
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::TextContent>() && event.content()) {
|
||||
body = event.get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
if (event.hasTextContent() && event.content()) {
|
||||
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
||||
#endif
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
}
|
||||
@@ -304,7 +281,7 @@ QString EventHandler::markdownBody(const Quotient::RoomEvent *event)
|
||||
|
||||
QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines)
|
||||
{
|
||||
if (event->isRedacted()) {
|
||||
if (event->isRedacted() && !event->isStateEvent()) {
|
||||
auto reason = event->redactedBecause()->reason();
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>") : i18n("<i>[This message was deleted: %1]</i>", reason.toHtmlEscaped());
|
||||
}
|
||||
@@ -477,13 +454,8 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
{
|
||||
TextHandler textHandler;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::FileContent>()) {
|
||||
QString fileCaption = event.get<EventContent::FileContent>()->originalName;
|
||||
#else
|
||||
if (event.hasFileContent()) {
|
||||
QString fileCaption = event.content()->fileInfo()->originalName;
|
||||
#endif
|
||||
if (fileCaption.isEmpty()) {
|
||||
fileCaption = event.plainBody();
|
||||
} else if (fileCaption != event.plainBody()) {
|
||||
@@ -494,13 +466,8 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
}
|
||||
|
||||
QString body;
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::TextContent>() && event.content()) {
|
||||
body = event.get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
if (event.hasTextContent() && event.content()) {
|
||||
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
||||
#endif
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
}
|
||||
@@ -531,7 +498,7 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
|
||||
qCWarning(EventHandling) << "genericBody called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (event->isRedacted()) {
|
||||
if (event->isRedacted() && !event->isStateEvent()) {
|
||||
return i18n("<i>[This message was deleted]</i>");
|
||||
}
|
||||
|
||||
@@ -715,28 +682,15 @@ QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Qu
|
||||
// Get the file info for the event.
|
||||
if (event->is<RoomMessageEvent>()) {
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (!roomMessageEvent->has<EventContent::FileContentBase>()) {
|
||||
#else
|
||||
if (!roomMessageEvent->hasFileContent()) {
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const auto content = roomMessageEvent->get<EventContent::FileContentBase>();
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, content.get(), eventId, false, false);
|
||||
#else
|
||||
const auto content = static_cast<const EventContent::FileContent *>(roomMessageEvent->content());
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, content, eventId, false, false);
|
||||
#endif
|
||||
// if filename isn't specifically given, it is in body
|
||||
// https://spec.matrix.org/latest/client-server-api/#mfile
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["filename"_ls] = content->commonInfo().originalName.isEmpty() ? roomMessageEvent->plainBody() : content->commonInfo().originalName;
|
||||
#else
|
||||
mediaInfo["filename"_ls] = (content->fileInfo()->originalName.isEmpty()) ? roomMessageEvent->plainBody() : content->fileInfo()->originalName;
|
||||
#endif
|
||||
|
||||
return mediaInfo;
|
||||
} else if (event->is<StickerEvent>()) {
|
||||
@@ -750,11 +704,7 @@ QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Qu
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const Quotient::EventContent::FileContentBase *fileContent,
|
||||
#else
|
||||
const Quotient::EventContent::TypedBase *fileContent,
|
||||
#endif
|
||||
const QString &eventId,
|
||||
bool isThumbnail,
|
||||
bool isSticker)
|
||||
@@ -762,18 +712,10 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
// Get the mxc URL for the media.
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (!fileContent->url().isValid() || fileContent->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
#else
|
||||
if (!fileContent->fileInfo()->url().isValid() || fileContent->fileInfo()->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
#endif
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QUrl source = room->makeMediaUrl(eventId, fileContent->url());
|
||||
#else
|
||||
QUrl source = room->makeMediaUrl(eventId, fileContent->fileInfo()->url());
|
||||
#endif
|
||||
|
||||
if (source.isValid()) {
|
||||
mediaInfo["source"_ls] = source;
|
||||
@@ -790,25 +732,15 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
mediaInfo["mimeIcon"_ls] = mimeType.iconName();
|
||||
|
||||
// Add media size if available.
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["size"_ls] = fileContent->commonInfo().payloadSize;
|
||||
#else
|
||||
mediaInfo["size"_ls] = static_cast<const EventContent::FileContent *>(fileContent)->fileInfo()->payloadSize;
|
||||
#endif
|
||||
|
||||
mediaInfo["isSticker"_ls] = isSticker;
|
||||
|
||||
// Add parameter depending on media type.
|
||||
if (mimeType.name().contains(QStringLiteral("image"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileContent)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["width"_ls] = castInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = castInfo->imageSize.height();
|
||||
#else
|
||||
const auto imageInfo = static_cast<const EventContent::ImageInfo *>(castInfo->fileInfo());
|
||||
mediaInfo["width"_ls] = imageInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = imageInfo->imageSize.height();
|
||||
#endif
|
||||
|
||||
// TODO: Images in certain formats (e.g. WebP) will be erroneously marked as animated, even if they are static.
|
||||
mediaInfo["animated"_ls] = QMovie::supportedFormats().contains(mimeType.preferredSuffix().toUtf8());
|
||||
@@ -892,31 +824,6 @@ QVariantMap EventHandler::getMediaInfoFromTumbnail(const NeoChatRoom *room, cons
|
||||
return thumbnailInfo;
|
||||
}
|
||||
|
||||
bool EventHandler::hasReply(const Quotient::RoomEvent *event, bool showFallbacks)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "hasReply called with event set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto relations = event->contentPart<QJsonObject>("m.relates_to"_ls);
|
||||
if (!relations.isEmpty()) {
|
||||
const bool hasReplyRelation = relations.contains("m.in_reply_to"_ls);
|
||||
bool isFallingBack = relations["is_falling_back"_ls].toBool();
|
||||
return hasReplyRelation && (showFallbacks ? true : !isFallingBack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString EventHandler::replyId(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "replyId called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
|
||||
}
|
||||
|
||||
Quotient::RoomMember EventHandler::replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
@@ -935,38 +842,6 @@ Quotient::RoomMember EventHandler::replyAuthor(const NeoChatRoom *room, const Qu
|
||||
}
|
||||
}
|
||||
|
||||
bool EventHandler::isThreaded(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "isThreaded called with event set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
|
||||
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls)
|
||||
|| (!event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls));
|
||||
}
|
||||
|
||||
QString EventHandler::threadRoot(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "threadRoot called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get the thread root ID from m.relates_to if it exists.
|
||||
if (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
|
||||
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls) {
|
||||
return 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 (!event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls)) {
|
||||
return id(event);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float EventHandler::latitude(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
|
||||
@@ -37,14 +37,6 @@ class NeoChatRoom;
|
||||
class EventHandler
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Return the ID of the event.
|
||||
*
|
||||
* Returns the transaction ID if the Matrix ID is empty, which may be the case
|
||||
* for a pending event.
|
||||
*/
|
||||
static QString id(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Get the display name of the event author.
|
||||
*
|
||||
@@ -220,20 +212,6 @@ public:
|
||||
*/
|
||||
static QVariantMap mediaInfo(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Whether the event is a reply to another in the timeline.
|
||||
*
|
||||
* @param showFallbacks whether message that have is_falling_back set true should
|
||||
* show the fallback reply. Leave true for non-threaded
|
||||
* timelines.
|
||||
*/
|
||||
static bool hasReply(const Quotient::RoomEvent *event, bool showFallbacks = true);
|
||||
|
||||
/**
|
||||
* @brief Return the Matrix ID of the event replied to.
|
||||
*/
|
||||
static QString replyId(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Get the author of the event replied to in context of the room.
|
||||
*
|
||||
@@ -249,20 +227,6 @@ public:
|
||||
*/
|
||||
static Quotient::RoomMember replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Whether the message is part of a thread.
|
||||
*
|
||||
* i.e. There is a rel_type of m.thread.
|
||||
*/
|
||||
static bool isThreaded(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Return the Matrix ID of the thread's root message.
|
||||
*
|
||||
* Empty if this not part of a thread.
|
||||
*/
|
||||
static QString threadRoot(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Return the latitude for the event.
|
||||
*
|
||||
@@ -290,11 +254,7 @@ private:
|
||||
|
||||
static QVariantMap getMediaInfoForEvent(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
QVariantMap static getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const Quotient::EventContent::FileContentBase *fileContent,
|
||||
#else
|
||||
const Quotient::EventContent::TypedBase *fileContent,
|
||||
#endif
|
||||
const QString &eventId,
|
||||
bool isThumbnail = false,
|
||||
bool isSticker = false);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "imagepackevent.h"
|
||||
#include <QJsonObject>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -11,10 +10,10 @@ ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
||||
{
|
||||
if (json.contains(QStringLiteral("pack"))) {
|
||||
pack = ImagePackEventContent::Pack{
|
||||
fromJson<Omittable<QString>>(json["pack"_ls].toObject()["display_name"_ls]),
|
||||
fromJson<Omittable<QUrl>>(json["pack"_ls].toObject()["avatar_url"_ls]),
|
||||
fromJson<Omittable<QStringList>>(json["pack"_ls].toObject()["usage"_ls]),
|
||||
fromJson<Omittable<QString>>(json["pack"_ls].toObject()["attribution"_ls]),
|
||||
fromJson<std::optional<QString>>(json["pack"_ls].toObject()["display_name"_ls]),
|
||||
fromJson<std::optional<QUrl>>(json["pack"_ls].toObject()["avatar_url"_ls]),
|
||||
fromJson<std::optional<QStringList>>(json["pack"_ls].toObject()["usage"_ls]),
|
||||
fromJson<std::optional<QString>>(json["pack"_ls].toObject()["attribution"_ls]),
|
||||
};
|
||||
} else {
|
||||
pack = std::nullopt;
|
||||
@@ -31,9 +30,9 @@ ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
||||
images += ImagePackImage{
|
||||
k,
|
||||
fromJson<QUrl>(json["images"_ls][k]["url"_ls].toString()),
|
||||
fromJson<Omittable<QString>>(json["images"_ls][k]["body"_ls]),
|
||||
fromJson<std::optional<QString>>(json["images"_ls][k]["body"_ls]),
|
||||
info,
|
||||
fromJson<Omittable<QStringList>>(json["images"_ls][k]["usage"_ls]),
|
||||
fromJson<std::optional<QStringList>>(json["images"_ls][k]["usage"_ls]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/e2ee/sssshandler.h>
|
||||
#include <Quotient/keyimport.h>
|
||||
#include <Quotient/keyverificationsession.h>
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
#include <Quotient/keyimport.h>
|
||||
#endif
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
@@ -43,11 +40,9 @@ struct ForeignSSSSHandler {
|
||||
QML_NAMED_ELEMENT(SSSSHandler)
|
||||
};
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
struct ForeignKeyImport {
|
||||
Q_GADGET
|
||||
QML_SINGLETON
|
||||
QML_FOREIGN(Quotient::KeyImport)
|
||||
QML_NAMED_ELEMENT(KeyImport)
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeochatAdd3PIdJob::NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Omittable<QJsonObject> &auth)
|
||||
NeochatAdd3PIdJob::NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const std::optional<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("Add3PIDJob"), makePath("/_matrix/client/v3", "/account/3pid/add"))
|
||||
{
|
||||
QJsonObject _dataJson;
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
class NeochatAdd3PIdJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Quotient::Omittable<QJsonObject> &auth = {});
|
||||
explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const std::optional<QJsonObject> &auth = {});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
|
||||
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const std::optional<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password")
|
||||
{
|
||||
QJsonObject _data;
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
class NeochatChangePasswordJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Quotient::Omittable<QJsonObject> &auth = {});
|
||||
explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const std::optional<QJsonObject> &auth = {});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const Omittable<QJsonObject> &auth)
|
||||
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("DisableDeviceJob"), "_matrix/client/v3/account/deactivate")
|
||||
{
|
||||
QJsonObject data;
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeoChatDeactivateAccountJob(const Quotient::Omittable<QJsonObject> &auth = {});
|
||||
explicit NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth = {});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
|
||||
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const std::optional<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId).toLatin1())
|
||||
{
|
||||
QJsonObject _data;
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
class NeochatDeleteDeviceJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeochatDeleteDeviceJob(const QString &deviceId, const Quotient::Omittable<QJsonObject> &auth = {});
|
||||
explicit NeochatDeleteDeviceJob(const QString &deviceId, const std::optional<QJsonObject> &auth = {});
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
// TODO: Upstream to libQuotient
|
||||
class NeochatGetCommonRoomsJob : public Quotient::BaseJob
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
#include "linkpreviewer.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/csapi/authed-content-repo.h>
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -61,7 +62,13 @@ void LinkPreviewer::loadUrlPreview()
|
||||
if (conn == nullptr) {
|
||||
return;
|
||||
}
|
||||
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url);
|
||||
|
||||
BaseJob *job = nullptr;
|
||||
if (conn->supportedMatrixSpecVersions().contains("v1.11"_L1)) {
|
||||
job = conn->callApi<GetUrlPreviewAuthedJob>(m_url);
|
||||
} else {
|
||||
QT_IGNORE_DEPRECATIONS(job = conn->callApi<GetUrlPreviewJob>(m_url);)
|
||||
}
|
||||
|
||||
connect(job, &BaseJob::success, this, [this, job, conn]() {
|
||||
const auto json = job->jsonData();
|
||||
|
||||
@@ -25,8 +25,7 @@ void LoginHelper::init()
|
||||
m_connection = new NeoChatConnection();
|
||||
m_matrixId = QString();
|
||||
m_password = QString();
|
||||
m_deviceName = QStringLiteral("NeoChat %1 %2 %3 %4")
|
||||
.arg(QSysInfo::machineHostName(), QSysInfo::productType(), QSysInfo::productVersion(), QSysInfo::currentCpuArchitecture());
|
||||
m_deviceName = QStringLiteral("NeoChat");
|
||||
m_supportsSso = false;
|
||||
m_supportsPassword = false;
|
||||
m_ssoUrl = QUrl();
|
||||
|
||||
@@ -13,6 +13,7 @@ LoginStep {
|
||||
id: root
|
||||
|
||||
FormCard.FormTextDelegate {
|
||||
textItem.wrapMode: Text.Wrap
|
||||
text: i18n("Please wait while your messages are loaded from the server. This might take a little while.")
|
||||
}
|
||||
FormCard.AbstractFormDelegate {
|
||||
|
||||
@@ -7,6 +7,7 @@ import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.settings
|
||||
@@ -90,11 +91,27 @@ Kirigami.Page {
|
||||
id: loadedAccounts
|
||||
model: AccountRegistry
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: model.userId
|
||||
id: delegate
|
||||
|
||||
required property string userId
|
||||
required property NeoChatConnection connection
|
||||
|
||||
text: QmlUtils.escapeString(connection.localUser.displayName)
|
||||
description: connection.localUser.id
|
||||
leadingPadding: Kirigami.Units.largeSpacing
|
||||
|
||||
onClicked: {
|
||||
Controller.activeConnection = model.connection;
|
||||
Controller.activeConnection = delegate.connection;
|
||||
root.connectionChosen();
|
||||
}
|
||||
leading: KirigamiComponents.Avatar {
|
||||
id: avatar
|
||||
name: delegate.text
|
||||
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
|
||||
source: delegate.connection.localUser.avatarUrl.toString().length > 0 ? delegate.connection.makeMediaUrl(delegate.connection.localUser.avatarUrl) : ""
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||
}
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
|
||||
@@ -140,7 +140,7 @@ int main(int argc, char *argv[])
|
||||
KAboutData about(QStringLiteral("neochat"),
|
||||
i18n("NeoChat"),
|
||||
QStringLiteral(NEOCHAT_VERSION_STRING),
|
||||
i18n("Matrix client"),
|
||||
i18n("Chat on Matrix"),
|
||||
KAboutLicense::GPL_V3,
|
||||
i18n("© 2018-2020 Black Hat, 2020-2024 KDE Community"));
|
||||
about.addAuthor(i18n("Carl Schwan"),
|
||||
@@ -271,7 +271,6 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
|
||||
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
|
||||
|
||||
if (parser.isSet("ignore-ssl-errors"_ls)) {
|
||||
|
||||
@@ -163,11 +163,7 @@ void AccountEmoticonModel::setEmoticonImage(int index, const QUrl &source)
|
||||
QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl source)
|
||||
{
|
||||
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
co_await qCoro(job.get(), &BaseJob::finished);
|
||||
#else
|
||||
co_await qCoro(job, &BaseJob::finished);
|
||||
#endif
|
||||
if (job->error() != BaseJob::NoError) {
|
||||
co_return;
|
||||
}
|
||||
@@ -189,11 +185,7 @@ QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl sourc
|
||||
QCoro::Task<void> AccountEmoticonModel::doAddEmoticon(QUrl source, QString shortcode, QString description, QString type)
|
||||
{
|
||||
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
co_await qCoro(job.get(), &BaseJob::finished);
|
||||
#else
|
||||
co_await qCoro(job, &BaseJob::finished);
|
||||
#endif
|
||||
if (job->error() != BaseJob::NoError) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "enums/messagetype.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
@@ -17,6 +18,7 @@
|
||||
|
||||
using Action = ActionsModel::Action;
|
||||
using namespace Quotient;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls, "#ffd500"_ls, "#ffff00"_ls, "#d4ff00"_ls, "#aaff00"_ls, "#80ff00"_ls,
|
||||
"#55ff00"_ls, "#2bff00"_ls, "#00ff00"_ls, "#00ff2b"_ls, "#00ff55"_ls, "#00ff80"_ls, "#00ffaa"_ls, "#00ffd5"_ls, "#00ffff"_ls,
|
||||
@@ -107,11 +109,10 @@ QList<ActionsModel::Action> actions{
|
||||
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
|
||||
}
|
||||
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
|
||||
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
|
||||
rainbowText,
|
||||
RoomMessageEvent::MsgType::Text,
|
||||
chatBarCache->replyId(),
|
||||
chatBarCache->editId());
|
||||
auto content = std::make_unique<Quotient::EventContent::TextContent>(rainbowText, u"text/html"_s);
|
||||
EventRelation relatesTo =
|
||||
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
|
||||
room->post<Quotient::RoomMessageEvent>("/rainbow %1"_L1.arg(text), MessageEventType::Text, std::move(content), relatesTo);
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -127,11 +128,10 @@ QList<ActionsModel::Action> actions{
|
||||
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
|
||||
}
|
||||
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
|
||||
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
|
||||
rainbowText,
|
||||
RoomMessageEvent::MsgType::Emote,
|
||||
chatBarCache->replyId(),
|
||||
chatBarCache->editId());
|
||||
auto content = std::make_unique<Quotient::EventContent::TextContent>(rainbowText, u"text/html"_s);
|
||||
EventRelation relatesTo =
|
||||
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
|
||||
room->post<Quotient::RoomMessageEvent>(u"/rainbow %1"_s.arg(text), MessageEventType::Text, std::move(content), relatesTo);
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -142,7 +142,7 @@ QList<ActionsModel::Action> actions{
|
||||
Action{
|
||||
QStringLiteral("plain"),
|
||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
room->postMessage(text, text.toHtmlEscaped(), RoomMessageEvent::MsgType::Text, {}, {});
|
||||
room->postPlainText(text.toHtmlEscaped());
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -154,11 +154,10 @@ QList<ActionsModel::Action> actions{
|
||||
QStringLiteral("spoiler"),
|
||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
|
||||
// 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());
|
||||
auto content = std::make_unique<Quotient::EventContent::TextContent>(u"<span data-mx-spoiler>%1</span>"_s.arg(text), u"text/html"_s);
|
||||
EventRelation relatesTo =
|
||||
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
|
||||
room->post<Quotient::RoomMessageEvent>(u"/spoiler %1"_s.arg(text), MessageEventType::Text, std::move(content), relatesTo);
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -574,3 +573,78 @@ QList<Action> &ActionsModel::allActions() const
|
||||
{
|
||||
return actions;
|
||||
}
|
||||
|
||||
bool ActionsModel::handleQuickEditAction(NeoChatRoom *room, const QString &messageText)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
|
||||
auto match = sed.match(messageText);
|
||||
if (match.hasMatch()) {
|
||||
const QString regex = match.captured(1);
|
||||
const QString replacement = match.captured(2).toHtmlEscaped();
|
||||
const QString flags = match.captured(3);
|
||||
|
||||
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
|
||||
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
|
||||
if (event->senderId() == room->localMember().id() && event->has<EventContent::TextContent>()) {
|
||||
QString originalString;
|
||||
if (event->content()) {
|
||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content().get())->body;
|
||||
} else {
|
||||
originalString = event->plainBody();
|
||||
}
|
||||
QString replaceId = event->id();
|
||||
const auto eventRelation = event->relatesTo();
|
||||
if (eventRelation && eventRelation->type == "m.replace"_L1) {
|
||||
replaceId = eventRelation->eventId;
|
||||
}
|
||||
|
||||
std::unique_ptr<EventContent::TextContent> content = nullptr;
|
||||
if (flags == "/g"_L1) {
|
||||
content = std::make_unique<Quotient::EventContent::TextContent>(originalString.replace(regex, replacement), u"text/html"_s);
|
||||
} else {
|
||||
content = std::make_unique<Quotient::EventContent::TextContent>(originalString.replace(regex, replacement), u"text/html"_s);
|
||||
}
|
||||
Quotient::EventRelation relatesTo = Quotient::EventRelation::replace(replaceId);
|
||||
room->post<Quotient::RoomMessageEvent>(messageText, event->msgtype(), std::move(content), relatesTo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<std::optional<QString>, std::optional<Quotient::RoomMessageEvent::MsgType>> ActionsModel::handleAction(NeoChatRoom *room, ChatBarCache *chatBarCache)
|
||||
{
|
||||
auto sendText = chatBarCache->sendText();
|
||||
const auto edited = handleQuickEditAction(room, sendText);
|
||||
if (edited) {
|
||||
return std::make_pair(std::nullopt, std::nullopt);
|
||||
}
|
||||
|
||||
std::optional<Quotient::RoomMessageEvent::MsgType> messageType = std::nullopt;
|
||||
if (sendText.startsWith(QLatin1Char('/'))) {
|
||||
for (const auto &action : ActionsModel::instance().allActions()) {
|
||||
if (sendText.indexOf(action.prefix) == 1
|
||||
&& (sendText.indexOf(" "_ls) == action.prefix.length() + 1 || sendText.length() == action.prefix.length() + 1)) {
|
||||
sendText = action.handle(sendText.mid(action.prefix.length() + 1).trimmed(), room, chatBarCache);
|
||||
if (action.messageType.has_value()) {
|
||||
messageType = action.messageType;
|
||||
}
|
||||
if (action.messageAction) {
|
||||
break;
|
||||
} else {
|
||||
return std::make_pair(std::nullopt, std::nullopt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(sendText, messageType);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,21 @@ public:
|
||||
*/
|
||||
QList<Action> &allActions() const;
|
||||
|
||||
/**
|
||||
* @brief Handle special sed style edit action.
|
||||
*
|
||||
* @return True if the message has a sed edit which was actioned. False otherwise.
|
||||
*/
|
||||
static bool handleQuickEditAction(NeoChatRoom *room, const QString &messageText);
|
||||
|
||||
/**
|
||||
* @brief Handle any action within the message contained in the given ChatBarCache.
|
||||
*
|
||||
* @return A modified or unmodified string that needs to be sent or an empty string if
|
||||
* the handled action replaces sending a normal message.
|
||||
*/
|
||||
static std::pair<std::optional<QString>, std::optional<Quotient::RoomMessageEvent::MsgType>> handleAction(NeoChatRoom *room, ChatBarCache *chatBarCache);
|
||||
|
||||
private:
|
||||
ActionsModel() = default;
|
||||
};
|
||||
|
||||
@@ -85,13 +85,7 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
|
||||
return m_filterModel->data(filterIndex, RoomListModel::CanonicalAliasRole);
|
||||
}
|
||||
if (role == IconNameRole) {
|
||||
auto mediaId = m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toString();
|
||||
if (mediaId.isEmpty()) {
|
||||
return QVariant();
|
||||
}
|
||||
if (m_room) {
|
||||
return m_room->connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(mediaId)));
|
||||
}
|
||||
return m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toString();
|
||||
}
|
||||
}
|
||||
if (m_autoCompletionType == Emoji) {
|
||||
|
||||
@@ -129,11 +129,7 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, m_connection->user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
auto innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connect(innerJob.get(), &BaseJob::success, this, onSuccess);
|
||||
#else
|
||||
connect(innerJob, &BaseJob::success, this, onSuccess);
|
||||
#endif
|
||||
} else {
|
||||
onSuccess();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &event
|
||||
: QAbstractListModel(parent)
|
||||
, m_room(room)
|
||||
, m_eventId(eventId)
|
||||
, m_isPending(isPending)
|
||||
, m_currentState(isPending ? Pending : Unknown)
|
||||
, m_isReply(isReply)
|
||||
{
|
||||
initializeModel();
|
||||
@@ -45,19 +45,27 @@ void MessageContentModel::initializeModel()
|
||||
Q_ASSERT(m_room != nullptr);
|
||||
Q_ASSERT(!m_eventId.isEmpty());
|
||||
|
||||
connect(this, &MessageContentModel::eventUnavailable, this, &MessageContentModel::getEvent);
|
||||
|
||||
connect(m_room, &NeoChatRoom::pendingEventAdded, this, [this]() {
|
||||
if (m_room != nullptr && m_currentState == Unknown) {
|
||||
initializeEvent();
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
|
||||
if (m_room != nullptr) {
|
||||
if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) {
|
||||
beginResetModel();
|
||||
m_isPending = false;
|
||||
m_eventId = serverEvent->id();
|
||||
initializeEvent();
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::pendingEventMerged, this, [this]() {
|
||||
if (m_room != nullptr && m_currentState == Pending) {
|
||||
initializeEvent();
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::addedMessages, this, [this](int fromIndex, int toIndex) {
|
||||
if (m_room != nullptr) {
|
||||
for (int i = fromIndex; i <= toIndex; i++) {
|
||||
@@ -143,20 +151,33 @@ void MessageContentModel::initializeModel()
|
||||
});
|
||||
|
||||
initializeEvent();
|
||||
updateReplyModel();
|
||||
if (m_currentState == Available || m_currentState == Pending) {
|
||||
updateReplyModel();
|
||||
}
|
||||
resetModel();
|
||||
}
|
||||
|
||||
void MessageContentModel::initializeEvent()
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr) {
|
||||
Q_EMIT eventUnavailable();
|
||||
if (m_currentState == UnAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto eventResult = m_room->getEvent(m_eventId);
|
||||
if (eventResult.first == nullptr) {
|
||||
if (m_currentState != Pending) {
|
||||
getEvent();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (eventResult.second) {
|
||||
m_currentState = Pending;
|
||||
} else {
|
||||
m_currentState = Available;
|
||||
}
|
||||
|
||||
if (m_eventSenderObject == nullptr) {
|
||||
auto senderId = event->senderId();
|
||||
auto senderId = eventResult.first->senderId();
|
||||
// A pending event might not have a sender ID set yet but in that case it must
|
||||
// be the local member.
|
||||
if (senderId.isEmpty()) {
|
||||
@@ -172,7 +193,6 @@ void MessageContentModel::getEvent()
|
||||
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
|
||||
if (m_room != nullptr) {
|
||||
if (eventId == m_eventId) {
|
||||
m_notFound = false;
|
||||
initializeEvent();
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
@@ -184,7 +204,7 @@ void MessageContentModel::getEvent()
|
||||
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventNotFound, this, [this](const QString &eventId) {
|
||||
if (m_room != nullptr) {
|
||||
if (eventId == m_eventId) {
|
||||
m_notFound = true;
|
||||
m_currentState = UnAvailable;
|
||||
resetModel();
|
||||
return true;
|
||||
}
|
||||
@@ -237,7 +257,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
const auto component = m_components[index.row()];
|
||||
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr) {
|
||||
if (event.first == nullptr) {
|
||||
if (role == DisplayRole) {
|
||||
if (m_isReply) {
|
||||
return i18n("Loading reply");
|
||||
@@ -252,7 +272,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
|
||||
if (role == DisplayRole) {
|
||||
if (m_notFound || m_room->connection()->isIgnored(m_eventSenderId)) {
|
||||
if (m_currentState == UnAvailable || m_room->connection()->isIgnored(m_eventSenderId)) {
|
||||
Kirigami::Platform::PlatformTheme *theme =
|
||||
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
|
||||
@@ -276,7 +296,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
if (!component.content.isEmpty()) {
|
||||
return component.content;
|
||||
}
|
||||
return EventHandler::richBody(m_room, event);
|
||||
return EventHandler::richBody(m_room, event.first);
|
||||
}
|
||||
if (role == ComponentTypeRole) {
|
||||
return component.type;
|
||||
@@ -285,53 +305,55 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return component.attributes;
|
||||
}
|
||||
if (role == EventIdRole) {
|
||||
return EventHandler::id(event);
|
||||
return event.first->displayId();
|
||||
}
|
||||
if (role == TimeRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
||||
return event->transactionId() == pendingEvent->transactionId();
|
||||
return event.first->transactionId() == pendingEvent->transactionId();
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return EventHandler::time(event, m_isPending, lastUpdated);
|
||||
return EventHandler::time(event.first, m_currentState == Pending, lastUpdated);
|
||||
}
|
||||
if (role == TimeStringRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
||||
return event->transactionId() == pendingEvent->transactionId();
|
||||
return event.first->transactionId() == pendingEvent->transactionId();
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return EventHandler::timeString(event, QStringLiteral("hh:mm"), m_isPending, lastUpdated);
|
||||
return EventHandler::timeString(event.first, QStringLiteral("hh:mm"), m_currentState == Pending, lastUpdated);
|
||||
}
|
||||
if (role == AuthorRole) {
|
||||
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
|
||||
}
|
||||
if (role == MediaInfoRole) {
|
||||
return EventHandler::mediaInfo(m_room, event);
|
||||
return EventHandler::mediaInfo(m_room, event.first);
|
||||
}
|
||||
if (role == FileTransferInfoRole) {
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(event));
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(event.first));
|
||||
}
|
||||
if (role == ItineraryModelRole) {
|
||||
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
||||
}
|
||||
if (role == LatitudeRole) {
|
||||
return EventHandler::latitude(event);
|
||||
return EventHandler::latitude(event.first);
|
||||
}
|
||||
if (role == LongitudeRole) {
|
||||
return EventHandler::longitude(event);
|
||||
return EventHandler::longitude(event.first);
|
||||
}
|
||||
if (role == AssetRole) {
|
||||
return EventHandler::locationAssetType(event);
|
||||
return EventHandler::locationAssetType(event.first);
|
||||
}
|
||||
if (role == PollHandlerRole) {
|
||||
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
|
||||
}
|
||||
if (role == ReplyEventIdRole) {
|
||||
return EventHandler::replyId(event);
|
||||
if (const auto roomMessageEvent = eventCast<const RoomMessageEvent>(event.first)) {
|
||||
return roomMessageEvent->replyEventId();
|
||||
}
|
||||
}
|
||||
if (role == ReplyAuthorRole) {
|
||||
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event));
|
||||
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event.first));
|
||||
}
|
||||
if (role == ReplyContentModelRole) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
||||
@@ -387,18 +409,17 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
|
||||
|
||||
void MessageContentModel::resetModel()
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
|
||||
beginResetModel();
|
||||
m_components.clear();
|
||||
|
||||
if (m_room->connection()->isIgnored(m_eventSenderId) || m_notFound) {
|
||||
if (m_room->connection()->isIgnored(m_eventSenderId) || m_currentState == UnAvailable) {
|
||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == nullptr) {
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event.first == nullptr) {
|
||||
m_components += MessageComponent{MessageComponentType::Loading, QString(), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
@@ -431,19 +452,19 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
||||
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr) {
|
||||
if (event.first == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<MessageComponent> newComponents;
|
||||
|
||||
if (eventCast<const Quotient::RoomMessageEvent>(event)
|
||||
&& eventCast<const Quotient::RoomMessageEvent>(event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
||||
if (roomMessageEvent && roomMessageEvent->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
||||
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
|
||||
return newComponents;
|
||||
}
|
||||
|
||||
if (event->isRedacted()) {
|
||||
if (event.first->isRedacted()) {
|
||||
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
return newComponents;
|
||||
}
|
||||
@@ -455,7 +476,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
if (isEditing) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
} else {
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event)));
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event.first)));
|
||||
}
|
||||
|
||||
if (m_room->urlPreviewEnabled()) {
|
||||
@@ -463,7 +484,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
}
|
||||
|
||||
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
|
||||
if (isThreading && !EventHandler::isThreaded(event)) {
|
||||
if (isThreading && roomMessageEvent && roomMessageEvent->isThreaded()) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
}
|
||||
|
||||
@@ -473,11 +494,15 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
void MessageContentModel::updateReplyModel()
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr || m_isReply) {
|
||||
if (event.first == nullptr || m_isReply) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EventHandler::hasReply(event) || (EventHandler::isThreaded(event) && NeoChatConfig::self()->threads())) {
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
||||
if (roomMessageEvent == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (!roomMessageEvent->isReply() || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) {
|
||||
if (m_replyModel) {
|
||||
delete m_replyModel;
|
||||
}
|
||||
@@ -488,7 +513,7 @@ void MessageContentModel::updateReplyModel()
|
||||
return;
|
||||
}
|
||||
|
||||
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event), true, false, this);
|
||||
m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(), true, false, this);
|
||||
|
||||
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
|
||||
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
|
||||
@@ -498,13 +523,13 @@ void MessageContentModel::updateReplyModel()
|
||||
QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentType::Type type)
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr) {
|
||||
if (event.first == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MessageComponentType::Text: {
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
return TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
@@ -515,26 +540,17 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
||||
case MessageComponentType::File: {
|
||||
QList<MessageComponent> components;
|
||||
components += MessageComponent{MessageComponentType::File, QString(), {}};
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
||||
|
||||
if (m_emptyItinerary) {
|
||||
if (!m_isReply) {
|
||||
auto fileTransferInfo = m_room->cachedFileTransferInfo(event);
|
||||
auto fileTransferInfo = m_room->cachedFileTransferInfo(event.first);
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->has<EventContent::FileContent>());
|
||||
const QMimeType mimeType = roomMessageEvent->get<EventContent::FileContent>()->mimeType;
|
||||
#else
|
||||
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->hasFileContent());
|
||||
const QMimeType mimeType = roomMessageEvent->content()->fileInfo()->mimeType;
|
||||
#endif
|
||||
if (mimeType.name() == QStringLiteral("text/plain") || mimeType.parentMimeTypes().contains(QStringLiteral("text/plain"))) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString originalName = roomMessageEvent->get<EventContent::FileContent>()->originalName;
|
||||
#else
|
||||
QString originalName = roomMessageEvent->content()->fileInfo()->originalName;
|
||||
#endif
|
||||
if (originalName.isEmpty()) {
|
||||
originalName = roomMessageEvent->plainBody();
|
||||
}
|
||||
@@ -576,19 +592,27 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
||||
case MessageComponentType::Image:
|
||||
case MessageComponentType::Audio:
|
||||
case MessageComponentType::Video: {
|
||||
if (!event->is<StickerEvent>()) {
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
QList<MessageComponent> components;
|
||||
components += MessageComponent{type, QString(), {}};
|
||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
components += TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
return components;
|
||||
if (!event.first->is<StickerEvent>()) {
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
||||
const auto fileContent = roomMessageEvent->get<EventContent::FileContentBase>();
|
||||
if (fileContent != nullptr) {
|
||||
const auto fileInfo = fileContent->commonInfo();
|
||||
const auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
// Do not attach the description to the image, if it's the same as the original filename.
|
||||
if (fileInfo.originalName != body) {
|
||||
QList<MessageComponent> components;
|
||||
components += MessageComponent{type, QString(), {}};
|
||||
components += TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
return components;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
[[fallthrough]];
|
||||
default:
|
||||
return {MessageComponent{type, QString(), {}}};
|
||||
}
|
||||
@@ -644,7 +668,7 @@ QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageCompon
|
||||
|
||||
void MessageContentModel::closeLinkPreview(int row)
|
||||
{
|
||||
if (row < 0 || row > m_components.size()) {
|
||||
if (row < 0 || row >= m_components.size()) {
|
||||
qWarning() << "closeLinkPreview() called with row" << row << "which does not exist. m_components.size() =" << m_components.size();
|
||||
return;
|
||||
}
|
||||
@@ -654,6 +678,7 @@ void MessageContentModel::closeLinkPreview(int row)
|
||||
m_removedLinkPreviews += m_components[row].attributes["link"_ls].toUrl();
|
||||
m_components.remove(row);
|
||||
m_components.squeeze();
|
||||
endResetModel();
|
||||
resetContent();
|
||||
}
|
||||
}
|
||||
@@ -661,17 +686,13 @@ void MessageContentModel::closeLinkPreview(int row)
|
||||
void MessageContentModel::updateItineraryModel()
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (m_room == nullptr || event == nullptr) {
|
||||
if (m_room == nullptr || event.first == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first)) {
|
||||
if (roomMessageEvent->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (roomMessageEvent->hasFileContent()) {
|
||||
#endif
|
||||
auto filePath = m_room->cachedFileTransferInfo(event).localPath;
|
||||
auto filePath = m_room->cachedFileTransferInfo(event.first).localPath;
|
||||
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
|
||||
delete m_itineraryModel;
|
||||
m_itineraryModel = nullptr;
|
||||
|
||||
@@ -31,6 +31,14 @@ class MessageContentModel : public QAbstractListModel
|
||||
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
|
||||
|
||||
public:
|
||||
enum MessageState {
|
||||
Unknown, /**< The message state is unknown. */
|
||||
Pending, /**< The message is a new pending message which the server has not yet acknowledged. */
|
||||
Available, /**< The message is available and acknowledged by the server. */
|
||||
UnAvailable, /**< The message can't be retrieved either because it doesn't exist or is blocked. */
|
||||
};
|
||||
Q_ENUM(MessageState)
|
||||
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
@@ -98,7 +106,6 @@ public:
|
||||
|
||||
Q_SIGNALS:
|
||||
void showAuthorChanged();
|
||||
void eventUnavailable();
|
||||
void eventUpdated();
|
||||
|
||||
private:
|
||||
@@ -107,10 +114,9 @@ private:
|
||||
QString m_eventSenderId;
|
||||
std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr;
|
||||
|
||||
bool m_isPending;
|
||||
MessageState m_currentState = Unknown;
|
||||
bool m_showAuthor = true;
|
||||
bool m_isReply;
|
||||
bool m_notFound = false;
|
||||
|
||||
void initializeModel();
|
||||
void initializeEvent();
|
||||
|
||||
@@ -160,12 +160,21 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
refreshLastUserEvents(i);
|
||||
}
|
||||
});
|
||||
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 0)
|
||||
connect(m_currentRoom, &Room::pendingEventAdded, this, [this](const Quotient::RoomEvent *event) {
|
||||
m_initialized = true;
|
||||
createEventObjects(event, true);
|
||||
beginInsertRows({}, 0, 0);
|
||||
endInsertRows();
|
||||
});
|
||||
#else
|
||||
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
||||
m_initialized = true;
|
||||
createEventObjects(event);
|
||||
createEventObjects(event, true);
|
||||
beginInsertRows({}, 0, 0);
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
||||
#endif
|
||||
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent *, int i) {
|
||||
Q_EMIT dataChanged(index(i, 0), index(i, 0), {IsPendingRole});
|
||||
if (i == 0) {
|
||||
@@ -492,7 +501,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return EventStatus::Hidden;
|
||||
}
|
||||
|
||||
if (EventHandler::isThreaded(&evt) && EventHandler::threadRoot(&evt) != EventHandler::id(&evt) && NeoChatConfig::threads()) {
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt);
|
||||
if (roomMessageEvent && roomMessageEvent->isThreaded() && roomMessageEvent->threadRootEventId() != evt.id() && NeoChatConfig::threads()) {
|
||||
return EventStatus::Hidden;
|
||||
}
|
||||
|
||||
@@ -500,16 +510,12 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == EventIdRole) {
|
||||
return EventHandler::id(&evt);
|
||||
return evt.displayId();
|
||||
}
|
||||
|
||||
if (role == ProgressInfoRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (e->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (e->hasFileContent()) {
|
||||
#endif
|
||||
return QVariant::fromValue(m_currentRoom->cachedFileTransferInfo(&evt));
|
||||
}
|
||||
}
|
||||
@@ -529,11 +535,18 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == IsThreadedRole) {
|
||||
return EventHandler::isThreaded(&evt);
|
||||
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
return roomMessageEvent->isThreaded();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == ThreadRootRole) {
|
||||
return EventHandler::threadRoot(&evt);
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt);
|
||||
if (roomMessageEvent && roomMessageEvent->isThreaded()) {
|
||||
return roomMessageEvent->threadRootEventId();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == ShowSectionRole) {
|
||||
@@ -622,7 +635,7 @@ int MessageEventModel::eventIdToRow(const QString &eventID) const
|
||||
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
|
||||
}
|
||||
|
||||
void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
|
||||
void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event, bool isPending)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
return;
|
||||
@@ -645,12 +658,14 @@ void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
|
||||
|
||||
if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) {
|
||||
if (!event->isStateEvent() || event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, eventId));
|
||||
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, eventId, false, isPending));
|
||||
}
|
||||
}
|
||||
|
||||
if (EventHandler::isThreaded(event) && !m_threadModels.contains(EventHandler::threadRoot(event))) {
|
||||
m_threadModels[EventHandler::threadRoot(event)] = QSharedPointer<ThreadModel>(new ThreadModel(EventHandler::threadRoot(event), m_currentRoom));
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
if (roomMessageEvent && roomMessageEvent->isThreaded() && !m_threadModels.contains(roomMessageEvent->threadRootEventId())) {
|
||||
m_threadModels[roomMessageEvent->threadRootEventId()] =
|
||||
QSharedPointer<ThreadModel>(new ThreadModel(roomMessageEvent->threadRootEventId(), m_currentRoom));
|
||||
}
|
||||
|
||||
// ReadMarkerModel handles updates to add and remove markers, we only need to
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user