Compare commits
95 Commits
work/carl/
...
v24.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee02abfe37 | ||
|
|
f0de235f37 | ||
|
|
9e9fe6d275 | ||
|
|
f4ca5f0f34 | ||
|
|
5f240fa05c | ||
|
|
1e29eca59a | ||
|
|
1f71ec3bf8 | ||
|
|
64c5ad88f6 | ||
|
|
fb5a3c1c5c | ||
|
|
4a5a83f94a | ||
|
|
133edc249f | ||
|
|
da30e66127 | ||
|
|
4516e1e0f4 | ||
|
|
bd80f65163 | ||
|
|
f828ecf282 | ||
|
|
a0483167c5 | ||
|
|
87288f508c | ||
|
|
dc184ed2fd | ||
|
|
49e1bf9ab1 | ||
|
|
74acf3f9dc | ||
|
|
38205d2791 | ||
|
|
81da926d4f | ||
|
|
1018fe5d3f | ||
|
|
58b32dd50f | ||
|
|
82184b895a | ||
|
|
da0f6f78a4 | ||
|
|
cfd06d064c | ||
|
|
a90e9ae92a | ||
|
|
8ab0002057 | ||
|
|
e1840be234 | ||
|
|
d6ecaaa344 | ||
|
|
0c08c2ab89 | ||
|
|
39ff11e059 | ||
|
|
93254431c5 | ||
|
|
fc14a8eac8 | ||
|
|
50759bb3ca | ||
|
|
23134d8e72 | ||
|
|
7cd095f76a | ||
|
|
d5c3054da4 | ||
|
|
ae12c838bd | ||
|
|
51727dd345 | ||
|
|
5611b000fb | ||
|
|
461896e228 | ||
|
|
e388536a03 | ||
|
|
61f22edd86 | ||
|
|
9e368691d6 | ||
|
|
dad2b3ec8f | ||
|
|
8821c37ff8 | ||
|
|
c105170eca | ||
|
|
b07c04eddc | ||
|
|
7aa0f68b10 | ||
|
|
cbdae4c312 | ||
|
|
9210940556 | ||
|
|
d8489527b4 | ||
|
|
64c9cd97de | ||
|
|
119a9890b1 | ||
|
|
69be6b5939 | ||
|
|
112152f2df | ||
|
|
2ef634a6cb | ||
|
|
fd31b4fb74 | ||
|
|
3b12520fa2 | ||
|
|
24718a5f72 | ||
|
|
db62bacc7e | ||
|
|
6500669b67 | ||
|
|
557d151ed4 | ||
|
|
95ffd485b4 | ||
|
|
ebd38fb435 | ||
|
|
a5b999e682 | ||
|
|
41d34fc0e4 | ||
|
|
b51194f90f | ||
|
|
80ac9e1ba7 | ||
|
|
e3874c824a | ||
|
|
6599c6b609 | ||
|
|
13d522221c | ||
|
|
dd8f926f32 | ||
|
|
258312e798 | ||
|
|
43d40c7e75 | ||
|
|
cbcc9a6514 | ||
|
|
625048610b | ||
|
|
fa47b67e3d | ||
|
|
9347a66acf | ||
|
|
317df56ffa | ||
|
|
fed9197716 | ||
|
|
1e892599e9 | ||
|
|
b7229ca0cf | ||
|
|
953b711823 | ||
|
|
01d903efd3 | ||
|
|
241dd81932 | ||
|
|
f6dfe0cbcf | ||
|
|
f10b97139c | ||
|
|
385c5b3405 | ||
|
|
7bc6f906f8 | ||
|
|
b8b1434a95 | ||
|
|
85c7a4bcb3 | ||
|
|
84b698a7e8 |
55
.reuse/dep5
Normal file
55
.reuse/dep5
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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,9 +7,9 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
# KDE Applications version, managed by release script.
|
# KDE Applications version, managed by release script.
|
||||||
set(RELEASE_SERVICE_VERSION_MAJOR "25")
|
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
||||||
set(RELEASE_SERVICE_VERSION_MINOR "03")
|
set(RELEASE_SERVICE_VERSION_MINOR "12")
|
||||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
set(RELEASE_SERVICE_VERSION_MICRO "0")
|
||||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||||
|
|
||||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||||
|
|||||||
84
REUSE.toml
84
REUSE.toml
@@ -1,84 +0,0 @@
|
|||||||
# 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"
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB |
@@ -83,15 +83,6 @@ def create_room():
|
|||||||
next_sync_payload = "sync_response_new_room"
|
next_sync_payload = "sync_response_new_room"
|
||||||
return response
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ private:
|
|||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void initTestCase();
|
void initTestCase();
|
||||||
|
|
||||||
|
void eventId();
|
||||||
|
void nullEventId();
|
||||||
void authorDisplayName();
|
void authorDisplayName();
|
||||||
void nullAuthorDisplayName();
|
void nullAuthorDisplayName();
|
||||||
void singleLineSidplayName();
|
void singleLineSidplayName();
|
||||||
@@ -54,8 +56,14 @@ private Q_SLOTS:
|
|||||||
void nullSubtitle();
|
void nullSubtitle();
|
||||||
void mediaInfo();
|
void mediaInfo();
|
||||||
void nullMediaInfo();
|
void nullMediaInfo();
|
||||||
|
void hasReply();
|
||||||
|
void nullHasReply();
|
||||||
|
void replyId();
|
||||||
|
void nullReplyId();
|
||||||
void replyAuthor();
|
void replyAuthor();
|
||||||
void nullReplyAuthor();
|
void nullReplyAuthor();
|
||||||
|
void thread();
|
||||||
|
void nullThread();
|
||||||
void location();
|
void location();
|
||||||
void nullLocation();
|
void nullLocation();
|
||||||
};
|
};
|
||||||
@@ -66,6 +74,17 @@ void EventHandlerTest::initTestCase()
|
|||||||
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-eventhandler-sync.json"));
|
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()
|
void EventHandlerTest::authorDisplayName()
|
||||||
{
|
{
|
||||||
QCOMPARE(EventHandler::authorDisplayName(room, room->messageEvents().at(1).get()), QStringLiteral("before"));
|
QCOMPARE(EventHandler::authorDisplayName(room, room->messageEvents().at(1).get()), QStringLiteral("before"));
|
||||||
@@ -99,9 +118,8 @@ void EventHandlerTest::time()
|
|||||||
{
|
{
|
||||||
const auto event = room->messageEvents().at(0).get();
|
const auto event = room->messageEvents().at(0).get();
|
||||||
|
|
||||||
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
|
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
|
||||||
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, QTimeZone(QTimeZone::UTC))),
|
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
|
||||||
QDateTime::fromMSecsSinceEpoch(1234, QTimeZone(QTimeZone::UTC)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventHandlerTest::nullTime()
|
void EventHandlerTest::nullTime()
|
||||||
@@ -120,19 +138,19 @@ void EventHandlerTest::timeString()
|
|||||||
KFormat format;
|
KFormat format;
|
||||||
|
|
||||||
QCOMPARE(EventHandler::timeString(event, false),
|
QCOMPARE(EventHandler::timeString(event, false),
|
||||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
|
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||||
QCOMPARE(EventHandler::timeString(event, true),
|
QCOMPARE(EventHandler::timeString(event, true),
|
||||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
|
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
|
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
|
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||||
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
|
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
|
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
|
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::LongFormat));
|
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
|
||||||
QCOMPARE(EventHandler::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
|
QCOMPARE(EventHandler::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::LongFormat));
|
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
|
||||||
QCOMPARE(EventHandler::timeString(event, QStringLiteral("hh:mm")),
|
QCOMPARE(EventHandler::timeString(event, QStringLiteral("hh:mm")),
|
||||||
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toString(QStringLiteral("hh:mm")));
|
QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toString(QStringLiteral("hh:mm")));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventHandlerTest::highlighted()
|
void EventHandlerTest::highlighted()
|
||||||
@@ -277,6 +295,30 @@ void EventHandlerTest::nullMediaInfo()
|
|||||||
QCOMPARE(EventHandler::mediaInfo(room, nullptr), QVariantMap());
|
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()
|
void EventHandlerTest::replyAuthor()
|
||||||
{
|
{
|
||||||
auto replyEvent = room->messageEvents().at(0).get();
|
auto replyEvent = room->messageEvents().at(0).get();
|
||||||
@@ -302,6 +344,29 @@ void EventHandlerTest::nullReplyAuthor()
|
|||||||
QCOMPARE(EventHandler::replyAuthor(room, nullptr), RoomMember());
|
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()
|
void EventHandlerTest::location()
|
||||||
{
|
{
|
||||||
QCOMPARE(EventHandler::latitude(room->messageEvents().at(7).get()), QStringLiteral("51.7035").toFloat());
|
QCOMPARE(EventHandler::latitude(room->messageEvents().at(7).get()), QStringLiteral("51.7035").toFloat());
|
||||||
|
|||||||
@@ -61,12 +61,10 @@
|
|||||||
<summary xml:lang="fi">Keskustelu Matrixissä</summary>
|
<summary xml:lang="fi">Keskustelu Matrixissä</summary>
|
||||||
<summary xml:lang="fr">Discuter sur Matrix</summary>
|
<summary xml:lang="fr">Discuter sur Matrix</summary>
|
||||||
<summary xml:lang="gl">Charlar en 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="hu">Csevegés Matrixon</summary>
|
||||||
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
|
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
|
||||||
<summary xml:lang="it">Chat su Matrix</summary>
|
<summary xml:lang="it">Chat su Matrix</summary>
|
||||||
<summary xml:lang="ka">ისაუბრეთ 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="nl">Chat op Matrix</summary>
|
||||||
<summary xml:lang="nn">Prat med via Matrix</summary>
|
<summary xml:lang="nn">Prat med via Matrix</summary>
|
||||||
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
|
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
|
||||||
@@ -293,7 +291,6 @@
|
|||||||
<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::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::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::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>
|
</custom>
|
||||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||||
<screenshots>
|
<screenshots>
|
||||||
|
|||||||
@@ -95,23 +95,18 @@ Comment[de]=Über Matrix unterhalten
|
|||||||
Comment[en_GB]=Chat on Matrix
|
Comment[en_GB]=Chat on Matrix
|
||||||
Comment[es]=Chat en Matrix
|
Comment[es]=Chat en Matrix
|
||||||
Comment[eu]=Berriketa Matrix-en
|
Comment[eu]=Berriketa Matrix-en
|
||||||
Comment[fi]=Keskustele Matrixissä
|
|
||||||
Comment[fr]=Clavarder sur Matrix
|
Comment[fr]=Clavarder sur Matrix
|
||||||
Comment[gl]=Charle en Matrix
|
Comment[gl]=Charle en Matrix
|
||||||
Comment[he]=התכתבות דרך Matrix
|
|
||||||
Comment[hu]=Csevegés Matrixon
|
Comment[hu]=Csevegés Matrixon
|
||||||
Comment[ia]=Conversation en ditecto sur Matrix
|
Comment[ia]=Conversation en ditecto sur Matrix
|
||||||
Comment[it]= su Matrix
|
Comment[it]= su Matrix
|
||||||
Comment[ka]=ჩატი Matrix-ზე
|
Comment[ka]=საუბარი Matrix-ზე
|
||||||
Comment[lv]=Tērzējiet „Matrix“ tīklā
|
|
||||||
Comment[nl]=Chat op Matrix
|
|
||||||
Comment[pl]=Rozmawiaj na Matriksie
|
Comment[pl]=Rozmawiaj na Matriksie
|
||||||
Comment[sl]=Klepet na Matrixu
|
Comment[sl]=Klepet na Matrixu
|
||||||
Comment[sv]=Chatta på Matrix
|
Comment[sv]=Chatta på Matrix
|
||||||
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
|
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
|
||||||
Comment[tr]=Matrix Üzerinde Sohbet Et
|
Comment[tr]=Matrix Üzerinde Sohbet Et
|
||||||
Comment[uk]=Спілкування у Matrix
|
Comment[uk]=Спілкування у Matrix
|
||||||
Comment[x-test]=xxChat on Matrixxx
|
|
||||||
Comment[zh_TW]=在 Matrix 上聊天
|
Comment[zh_TW]=在 Matrix 上聊天
|
||||||
MimeType=x-scheme-handler/matrix;
|
MimeType=x-scheme-handler/matrix;
|
||||||
Exec=neochat %u
|
Exec=neochat %u
|
||||||
|
|||||||
1015
po/ar/neochat.po
1015
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1080
po/az/neochat.po
1080
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1290
po/ca/neochat.po
1290
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1154
po/cs/neochat.po
1154
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1064
po/da/neochat.po
1064
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1128
po/de/neochat.po
1128
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1101
po/el/neochat.po
1101
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1148
po/en_GB/neochat.po
1148
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1164
po/eo/neochat.po
1164
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
994
po/es/neochat.po
994
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1038
po/eu/neochat.po
1038
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1152
po/fi/neochat.po
1152
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1012
po/fr/neochat.po
1012
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1008
po/gl/neochat.po
1008
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
1166
po/hu/neochat.po
1166
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1144
po/ia/neochat.po
1144
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1117
po/id/neochat.po
1117
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1057
po/ie/neochat.po
1057
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1144
po/it/neochat.po
1144
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
927
po/ja/neochat.po
927
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
993
po/ka/neochat.po
993
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1141
po/ko/neochat.po
1141
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
927
po/lt/neochat.po
927
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1169
po/lv/neochat.po
1169
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1003
po/nl/neochat.po
1003
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1147
po/nn/neochat.po
1147
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1069
po/pa/neochat.po
1069
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1166
po/pl/neochat.po
1166
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1117
po/pt/neochat.po
1117
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1083
po/pt_BR/neochat.po
1083
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1140
po/ru/neochat.po
1140
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1643
po/sk/neochat.po
1643
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
994
po/sl/neochat.po
994
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1011
po/sv/neochat.po
1011
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1006
po/ta/neochat.po
1006
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1035
po/tok/neochat.po
1035
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1000
po/tr/neochat.po
1000
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
996
po/uk/neochat.po
996
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1140
po/zh_TW/neochat.po
1140
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -194,8 +194,6 @@ add_library(neochat STATIC
|
|||||||
models/threadmodel.h
|
models/threadmodel.h
|
||||||
enums/messagetype.h
|
enums/messagetype.h
|
||||||
messagecomponent.h
|
messagecomponent.h
|
||||||
enums/roomsortparameter.cpp
|
|
||||||
enums/roomsortparameter.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||||
@@ -535,7 +533,6 @@ if(ANDROID)
|
|||||||
"kde"
|
"kde"
|
||||||
"list-remove-symbolic"
|
"list-remove-symbolic"
|
||||||
"edit-delete"
|
"edit-delete"
|
||||||
"user-home-symbolic"
|
|
||||||
)
|
)
|
||||||
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
|
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
|
||||||
else()
|
else()
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "texthandler.h"
|
#include "texthandler.h"
|
||||||
|
|
||||||
using namespace Qt::StringLiterals;
|
|
||||||
|
|
||||||
ChatBarCache::ChatBarCache(QObject *parent)
|
ChatBarCache::ChatBarCache(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
@@ -321,25 +319,7 @@ void ChatBarCache::postMessage()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isReply = !replyId().isEmpty();
|
room->postMessage(text(), sendText, *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), replyId(), editId(), threadId());
|
||||||
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();
|
clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
ColorSchemer::ColorSchemer(QObject *parent)
|
ColorSchemer::ColorSchemer(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
|
, c(new KColorSchemeManager(this))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,17 +18,17 @@ ColorSchemer::~ColorSchemer()
|
|||||||
|
|
||||||
QAbstractItemModel *ColorSchemer::model() const
|
QAbstractItemModel *ColorSchemer::model() const
|
||||||
{
|
{
|
||||||
return KColorSchemeManager::instance()->model();
|
return c->model();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorSchemer::apply(int idx)
|
void ColorSchemer::apply(int idx)
|
||||||
{
|
{
|
||||||
KColorSchemeManager::instance()->activateScheme(KColorSchemeManager::instance()->model()->index(idx, 0));
|
c->activateScheme(c->model()->index(idx, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
int ColorSchemer::indexForCurrentScheme()
|
int ColorSchemer::indexForCurrentScheme()
|
||||||
{
|
{
|
||||||
return KColorSchemeManager::instance()->indexForSchemeId(KColorSchemeManager::instance()->activeSchemeId()).row();
|
return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_colorschemer.cpp"
|
#include "moc_colorschemer.cpp"
|
||||||
|
|||||||
@@ -49,4 +49,7 @@ public:
|
|||||||
* @sa KColorScheme
|
* @sa KColorScheme
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE int indexForCurrentScheme();
|
Q_INVOKABLE int indexForCurrentScheme();
|
||||||
|
|
||||||
|
private:
|
||||||
|
KColorSchemeManager *c;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,28 +33,4 @@ 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,22 +21,19 @@ ColumnLayout {
|
|||||||
title: i18nc("@title", "Choose Room")
|
title: i18nc("@title", "Choose Room")
|
||||||
}
|
}
|
||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
FormCard.FormButtonDelegate {
|
FormCard.FormComboBoxDelegate {
|
||||||
text: root.room?.displayNameForHtml ?? i18nc("@info", "No room selected")
|
id: roomComboBox
|
||||||
description: i18nc("@info", "Click to choose a room");
|
text: i18n("Room")
|
||||||
|
textRole: "escapedDisplayName"
|
||||||
onClicked: {
|
valueRole: "roomId"
|
||||||
let dialog = root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
displayText: RoomManager.roomListModel.data(RoomManager.roomListModel.index(currentIndex, 0), RoomListModel.EscapedDisplayNameRole)
|
||||||
connection: root.connection,
|
model: RoomManager.roomListModel
|
||||||
}, {
|
currentIndex: 0
|
||||||
title: i18nc("@title:dialog", "Choose Room"),
|
displayMode: FormCard.FormComboBoxDelegate.Page
|
||||||
width: Kirigami.Units.gridUnit * 24
|
Component.onCompleted: currentIndex = RoomManager.roomListModel.rowForRoom(root.room)
|
||||||
});
|
onCurrentValueChanged: root.room = RoomManager.roomListModel.roomByAliasOrId(roomComboBox.currentValue)
|
||||||
dialog.chosen.connect(id => root.room = root.connection.room(id))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
FormCard.FormTextDelegate {
|
FormCard.FormTextDelegate {
|
||||||
visible: root.room
|
|
||||||
text: i18n("Room Id: %1", root.room.id)
|
text: i18n("Room Id: %1", root.room.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ FormCard.FormCardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openEventSource(stateKey: string): void {
|
function openEventSource(stateKey: string): void {
|
||||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||||
model: stateKeysModel,
|
model: stateKeysModel,
|
||||||
allowEdit: true,
|
allowEdit: true,
|
||||||
room: root.room,
|
room: root.room,
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
// 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());
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
// 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,6 +49,16 @@ Q_DECLARE_FLAGS(MemberChanges, MemberChange)
|
|||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(MemberChanges)
|
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)
|
QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
|
||||||
{
|
{
|
||||||
if (room == nullptr) {
|
if (room == nullptr) {
|
||||||
@@ -60,7 +70,7 @@ QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient:
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is<RoomMemberEvent>(*event) && event->unsignedJson()[QStringLiteral("prev_content")].toObject().contains("displayname"_L1)
|
if (is<RoomMemberEvent>(*event) && !event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].isNull()
|
||||||
&& event->stateKey() == event->senderId()) {
|
&& event->stateKey() == event->senderId()) {
|
||||||
auto previousDisplayName = event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].toString().toHtmlEscaped();
|
auto previousDisplayName = event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].toString().toHtmlEscaped();
|
||||||
if (previousDisplayName.isEmpty()) {
|
if (previousDisplayName.isEmpty()) {
|
||||||
@@ -281,7 +291,7 @@ QString EventHandler::markdownBody(const Quotient::RoomEvent *event)
|
|||||||
|
|
||||||
QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines)
|
QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines)
|
||||||
{
|
{
|
||||||
if (event->isRedacted() && !event->isStateEvent()) {
|
if (event->isRedacted()) {
|
||||||
auto reason = event->redactedBecause()->reason();
|
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());
|
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>") : i18n("<i>[This message was deleted: %1]</i>", reason.toHtmlEscaped());
|
||||||
}
|
}
|
||||||
@@ -498,7 +508,7 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
|
|||||||
qCWarning(EventHandling) << "genericBody called with event set to nullptr.";
|
qCWarning(EventHandling) << "genericBody called with event set to nullptr.";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
if (event->isRedacted() && !event->isStateEvent()) {
|
if (event->isRedacted()) {
|
||||||
return i18n("<i>[This message was deleted]</i>");
|
return i18n("<i>[This message was deleted]</i>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,6 +834,31 @@ QVariantMap EventHandler::getMediaInfoFromTumbnail(const NeoChatRoom *room, cons
|
|||||||
return thumbnailInfo;
|
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)
|
Quotient::RoomMember EventHandler::replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||||
{
|
{
|
||||||
if (room == nullptr) {
|
if (room == nullptr) {
|
||||||
@@ -842,6 +877,38 @@ 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)
|
float EventHandler::latitude(const Quotient::RoomEvent *event)
|
||||||
{
|
{
|
||||||
if (event == nullptr) {
|
if (event == nullptr) {
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ class NeoChatRoom;
|
|||||||
class EventHandler
|
class EventHandler
|
||||||
{
|
{
|
||||||
public:
|
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.
|
* @brief Get the display name of the event author.
|
||||||
*
|
*
|
||||||
@@ -212,6 +220,20 @@ public:
|
|||||||
*/
|
*/
|
||||||
static QVariantMap mediaInfo(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
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.
|
* @brief Get the author of the event replied to in context of the room.
|
||||||
*
|
*
|
||||||
@@ -227,6 +249,20 @@ public:
|
|||||||
*/
|
*/
|
||||||
static Quotient::RoomMember replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
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.
|
* @brief Return the latitude for the event.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,12 +4,11 @@
|
|||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
|
|
||||||
#include <Quotient/connection.h>
|
#include <Quotient/connection.h>
|
||||||
#include <Quotient/csapi/authed-content-repo.h>
|
|
||||||
#include <Quotient/csapi/content-repo.h>
|
#include <Quotient/csapi/content-repo.h>
|
||||||
|
|
||||||
#include <Quotient/events/roommessageevent.h>
|
#include <Quotient/events/roommessageevent.h>
|
||||||
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
|
#include "neochatconnection.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -62,13 +61,7 @@ void LinkPreviewer::loadUrlPreview()
|
|||||||
if (conn == nullptr) {
|
if (conn == nullptr) {
|
||||||
return;
|
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]() {
|
connect(job, &BaseJob::success, this, [this, job, conn]() {
|
||||||
const auto json = job->jsonData();
|
const auto json = job->jsonData();
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ int main(int argc, char *argv[])
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||||
|
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
|
||||||
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
|
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
|
||||||
|
|
||||||
if (parser.isSet("ignore-ssl-errors"_ls)) {
|
if (parser.isSet("ignore-ssl-errors"_ls)) {
|
||||||
|
|||||||
@@ -109,10 +109,11 @@ QList<ActionsModel::Action> actions{
|
|||||||
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
|
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.
|
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
|
||||||
auto content = std::make_unique<Quotient::EventContent::TextContent>(rainbowText, u"text/html"_s);
|
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
|
||||||
EventRelation relatesTo =
|
rainbowText,
|
||||||
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
|
RoomMessageEvent::MsgType::Text,
|
||||||
room->post<Quotient::RoomMessageEvent>("/rainbow %1"_L1.arg(text), MessageEventType::Text, std::move(content), relatesTo);
|
chatBarCache->replyId(),
|
||||||
|
chatBarCache->editId());
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -128,10 +129,11 @@ QList<ActionsModel::Action> actions{
|
|||||||
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
|
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.
|
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
|
||||||
auto content = std::make_unique<Quotient::EventContent::TextContent>(rainbowText, u"text/html"_s);
|
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
|
||||||
EventRelation relatesTo =
|
rainbowText,
|
||||||
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
|
RoomMessageEvent::MsgType::Emote,
|
||||||
room->post<Quotient::RoomMessageEvent>(u"/rainbow %1"_s.arg(text), MessageEventType::Text, std::move(content), relatesTo);
|
chatBarCache->replyId(),
|
||||||
|
chatBarCache->editId());
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -142,7 +144,7 @@ QList<ActionsModel::Action> actions{
|
|||||||
Action{
|
Action{
|
||||||
QStringLiteral("plain"),
|
QStringLiteral("plain"),
|
||||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||||
room->postPlainText(text.toHtmlEscaped());
|
room->postMessage(text, text.toHtmlEscaped(), RoomMessageEvent::MsgType::Text, {}, {});
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -154,10 +156,11 @@ QList<ActionsModel::Action> actions{
|
|||||||
QStringLiteral("spoiler"),
|
QStringLiteral("spoiler"),
|
||||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
|
[](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.
|
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
|
||||||
auto content = std::make_unique<Quotient::EventContent::TextContent>(u"<span data-mx-spoiler>%1</span>"_s.arg(text), u"text/html"_s);
|
room->postMessage(QStringLiteral("/spoiler %1").arg(text),
|
||||||
EventRelation relatesTo =
|
QStringLiteral("<span data-mx-spoiler>%1</span>").arg(text),
|
||||||
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
|
RoomMessageEvent::MsgType::Text,
|
||||||
room->post<Quotient::RoomMessageEvent>(u"/spoiler %1"_s.arg(text), MessageEventType::Text, std::move(content), relatesTo);
|
chatBarCache->replyId(),
|
||||||
|
chatBarCache->editId());
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -602,15 +605,15 @@ bool ActionsModel::handleQuickEditAction(NeoChatRoom *room, const QString &messa
|
|||||||
if (eventRelation && eventRelation->type == "m.replace"_L1) {
|
if (eventRelation && eventRelation->type == "m.replace"_L1) {
|
||||||
replaceId = eventRelation->eventId;
|
replaceId = eventRelation->eventId;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<EventContent::TextContent> content = nullptr;
|
|
||||||
if (flags == "/g"_L1) {
|
if (flags == "/g"_L1) {
|
||||||
content = std::make_unique<Quotient::EventContent::TextContent>(originalString.replace(regex, replacement), u"text/html"_s);
|
room->postHtmlMessage(messageText, originalString.replace(regex, replacement), event->msgtype(), {}, replaceId);
|
||||||
} else {
|
} else {
|
||||||
content = std::make_unique<Quotient::EventContent::TextContent>(originalString.replace(regex, replacement), u"text/html"_s);
|
room->postHtmlMessage(messageText,
|
||||||
|
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
||||||
|
event->msgtype(),
|
||||||
|
{},
|
||||||
|
replaceId);
|
||||||
}
|
}
|
||||||
Quotient::EventRelation relatesTo = Quotient::EventRelation::replace(replaceId);
|
|
||||||
room->post<Quotient::RoomMessageEvent>(messageText, event->msgtype(), std::move(content), relatesTo);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &event
|
|||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, m_room(room)
|
, m_room(room)
|
||||||
, m_eventId(eventId)
|
, m_eventId(eventId)
|
||||||
, m_currentState(isPending ? Pending : Unknown)
|
, m_isPending(isPending)
|
||||||
, m_isReply(isReply)
|
, m_isReply(isReply)
|
||||||
{
|
{
|
||||||
initializeModel();
|
initializeModel();
|
||||||
@@ -45,27 +45,19 @@ void MessageContentModel::initializeModel()
|
|||||||
Q_ASSERT(m_room != nullptr);
|
Q_ASSERT(m_room != nullptr);
|
||||||
Q_ASSERT(!m_eventId.isEmpty());
|
Q_ASSERT(!m_eventId.isEmpty());
|
||||||
|
|
||||||
connect(m_room, &NeoChatRoom::pendingEventAdded, this, [this]() {
|
connect(this, &MessageContentModel::eventUnavailable, this, &MessageContentModel::getEvent);
|
||||||
if (m_room != nullptr && m_currentState == Unknown) {
|
|
||||||
initializeEvent();
|
|
||||||
updateReplyModel();
|
|
||||||
resetModel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
|
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
|
||||||
if (m_room != nullptr) {
|
if (m_room != nullptr) {
|
||||||
if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) {
|
if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) {
|
||||||
|
beginResetModel();
|
||||||
|
m_isPending = false;
|
||||||
m_eventId = serverEvent->id();
|
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) {
|
connect(m_room, &NeoChatRoom::addedMessages, this, [this](int fromIndex, int toIndex) {
|
||||||
if (m_room != nullptr) {
|
if (m_room != nullptr) {
|
||||||
for (int i = fromIndex; i <= toIndex; i++) {
|
for (int i = fromIndex; i <= toIndex; i++) {
|
||||||
@@ -151,33 +143,20 @@ void MessageContentModel::initializeModel()
|
|||||||
});
|
});
|
||||||
|
|
||||||
initializeEvent();
|
initializeEvent();
|
||||||
if (m_currentState == Available || m_currentState == Pending) {
|
updateReplyModel();
|
||||||
updateReplyModel();
|
|
||||||
}
|
|
||||||
resetModel();
|
resetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageContentModel::initializeEvent()
|
void MessageContentModel::initializeEvent()
|
||||||
{
|
{
|
||||||
if (m_currentState == UnAvailable) {
|
const auto event = m_room->getEvent(m_eventId);
|
||||||
|
if (event == nullptr) {
|
||||||
|
Q_EMIT eventUnavailable();
|
||||||
return;
|
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) {
|
if (m_eventSenderObject == nullptr) {
|
||||||
auto senderId = eventResult.first->senderId();
|
auto senderId = event->senderId();
|
||||||
// A pending event might not have a sender ID set yet but in that case it must
|
// A pending event might not have a sender ID set yet but in that case it must
|
||||||
// be the local member.
|
// be the local member.
|
||||||
if (senderId.isEmpty()) {
|
if (senderId.isEmpty()) {
|
||||||
@@ -193,6 +172,7 @@ void MessageContentModel::getEvent()
|
|||||||
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
|
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
|
||||||
if (m_room != nullptr) {
|
if (m_room != nullptr) {
|
||||||
if (eventId == m_eventId) {
|
if (eventId == m_eventId) {
|
||||||
|
m_notFound = false;
|
||||||
initializeEvent();
|
initializeEvent();
|
||||||
updateReplyModel();
|
updateReplyModel();
|
||||||
resetModel();
|
resetModel();
|
||||||
@@ -204,7 +184,7 @@ void MessageContentModel::getEvent()
|
|||||||
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventNotFound, this, [this](const QString &eventId) {
|
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventNotFound, this, [this](const QString &eventId) {
|
||||||
if (m_room != nullptr) {
|
if (m_room != nullptr) {
|
||||||
if (eventId == m_eventId) {
|
if (eventId == m_eventId) {
|
||||||
m_currentState = UnAvailable;
|
m_notFound = true;
|
||||||
resetModel();
|
resetModel();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -257,7 +237,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
const auto component = m_components[index.row()];
|
const auto component = m_components[index.row()];
|
||||||
|
|
||||||
const auto event = m_room->getEvent(m_eventId);
|
const auto event = m_room->getEvent(m_eventId);
|
||||||
if (event.first == nullptr) {
|
if (event == nullptr) {
|
||||||
if (role == DisplayRole) {
|
if (role == DisplayRole) {
|
||||||
if (m_isReply) {
|
if (m_isReply) {
|
||||||
return i18n("Loading reply");
|
return i18n("Loading reply");
|
||||||
@@ -272,7 +252,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == DisplayRole) {
|
if (role == DisplayRole) {
|
||||||
if (m_currentState == UnAvailable || m_room->connection()->isIgnored(m_eventSenderId)) {
|
if (m_notFound || m_room->connection()->isIgnored(m_eventSenderId)) {
|
||||||
Kirigami::Platform::PlatformTheme *theme =
|
Kirigami::Platform::PlatformTheme *theme =
|
||||||
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||||
|
|
||||||
@@ -296,7 +276,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
if (!component.content.isEmpty()) {
|
if (!component.content.isEmpty()) {
|
||||||
return component.content;
|
return component.content;
|
||||||
}
|
}
|
||||||
return EventHandler::richBody(m_room, event.first);
|
return EventHandler::richBody(m_room, event);
|
||||||
}
|
}
|
||||||
if (role == ComponentTypeRole) {
|
if (role == ComponentTypeRole) {
|
||||||
return component.type;
|
return component.type;
|
||||||
@@ -305,55 +285,53 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
return component.attributes;
|
return component.attributes;
|
||||||
}
|
}
|
||||||
if (role == EventIdRole) {
|
if (role == EventIdRole) {
|
||||||
return event.first->displayId();
|
return EventHandler::id(event);
|
||||||
}
|
}
|
||||||
if (role == TimeRole) {
|
if (role == TimeRole) {
|
||||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
||||||
return event.first->transactionId() == pendingEvent->transactionId();
|
return event->transactionId() == pendingEvent->transactionId();
|
||||||
});
|
});
|
||||||
|
|
||||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||||
return EventHandler::time(event.first, m_currentState == Pending, lastUpdated);
|
return EventHandler::time(event, m_isPending, lastUpdated);
|
||||||
}
|
}
|
||||||
if (role == TimeStringRole) {
|
if (role == TimeStringRole) {
|
||||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
||||||
return event.first->transactionId() == pendingEvent->transactionId();
|
return event->transactionId() == pendingEvent->transactionId();
|
||||||
});
|
});
|
||||||
|
|
||||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||||
return EventHandler::timeString(event.first, QStringLiteral("hh:mm"), m_currentState == Pending, lastUpdated);
|
return EventHandler::timeString(event, QStringLiteral("hh:mm"), m_isPending, lastUpdated);
|
||||||
}
|
}
|
||||||
if (role == AuthorRole) {
|
if (role == AuthorRole) {
|
||||||
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
|
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
|
||||||
}
|
}
|
||||||
if (role == MediaInfoRole) {
|
if (role == MediaInfoRole) {
|
||||||
return EventHandler::mediaInfo(m_room, event.first);
|
return EventHandler::mediaInfo(m_room, event);
|
||||||
}
|
}
|
||||||
if (role == FileTransferInfoRole) {
|
if (role == FileTransferInfoRole) {
|
||||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(event.first));
|
return QVariant::fromValue(m_room->cachedFileTransferInfo(event));
|
||||||
}
|
}
|
||||||
if (role == ItineraryModelRole) {
|
if (role == ItineraryModelRole) {
|
||||||
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
||||||
}
|
}
|
||||||
if (role == LatitudeRole) {
|
if (role == LatitudeRole) {
|
||||||
return EventHandler::latitude(event.first);
|
return EventHandler::latitude(event);
|
||||||
}
|
}
|
||||||
if (role == LongitudeRole) {
|
if (role == LongitudeRole) {
|
||||||
return EventHandler::longitude(event.first);
|
return EventHandler::longitude(event);
|
||||||
}
|
}
|
||||||
if (role == AssetRole) {
|
if (role == AssetRole) {
|
||||||
return EventHandler::locationAssetType(event.first);
|
return EventHandler::locationAssetType(event);
|
||||||
}
|
}
|
||||||
if (role == PollHandlerRole) {
|
if (role == PollHandlerRole) {
|
||||||
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
|
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
|
||||||
}
|
}
|
||||||
if (role == ReplyEventIdRole) {
|
if (role == ReplyEventIdRole) {
|
||||||
if (const auto roomMessageEvent = eventCast<const RoomMessageEvent>(event.first)) {
|
return EventHandler::replyId(event);
|
||||||
return roomMessageEvent->replyEventId();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (role == ReplyAuthorRole) {
|
if (role == ReplyAuthorRole) {
|
||||||
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event.first));
|
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event));
|
||||||
}
|
}
|
||||||
if (role == ReplyContentModelRole) {
|
if (role == ReplyContentModelRole) {
|
||||||
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
||||||
@@ -409,17 +387,18 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
|
|||||||
|
|
||||||
void MessageContentModel::resetModel()
|
void MessageContentModel::resetModel()
|
||||||
{
|
{
|
||||||
|
const auto event = m_room->getEvent(m_eventId);
|
||||||
|
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_components.clear();
|
m_components.clear();
|
||||||
|
|
||||||
if (m_room->connection()->isIgnored(m_eventSenderId) || m_currentState == UnAvailable) {
|
if (m_room->connection()->isIgnored(m_eventSenderId) || m_notFound) {
|
||||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||||
endResetModel();
|
endResetModel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto event = m_room->getEvent(m_eventId);
|
if (event == nullptr) {
|
||||||
if (event.first == nullptr) {
|
|
||||||
m_components += MessageComponent{MessageComponentType::Loading, QString(), {}};
|
m_components += MessageComponent{MessageComponentType::Loading, QString(), {}};
|
||||||
endResetModel();
|
endResetModel();
|
||||||
return;
|
return;
|
||||||
@@ -452,19 +431,19 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
|||||||
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
|
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
|
||||||
{
|
{
|
||||||
const auto event = m_room->getEvent(m_eventId);
|
const auto event = m_room->getEvent(m_eventId);
|
||||||
if (event.first == nullptr) {
|
if (event == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<MessageComponent> newComponents;
|
QList<MessageComponent> newComponents;
|
||||||
|
|
||||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
if (eventCast<const Quotient::RoomMessageEvent>(event)
|
||||||
if (roomMessageEvent && roomMessageEvent->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
&& eventCast<const Quotient::RoomMessageEvent>(event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
||||||
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
|
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
|
||||||
return newComponents;
|
return newComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.first->isRedacted()) {
|
if (event->isRedacted()) {
|
||||||
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
|
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||||
return newComponents;
|
return newComponents;
|
||||||
}
|
}
|
||||||
@@ -476,7 +455,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
|||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||||
} else {
|
} else {
|
||||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event.first)));
|
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_room->urlPreviewEnabled()) {
|
if (m_room->urlPreviewEnabled()) {
|
||||||
@@ -484,7 +463,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
|
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
|
||||||
if (isThreading && roomMessageEvent && roomMessageEvent->isThreaded()) {
|
if (isThreading && !EventHandler::isThreaded(event)) {
|
||||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,15 +473,11 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
|||||||
void MessageContentModel::updateReplyModel()
|
void MessageContentModel::updateReplyModel()
|
||||||
{
|
{
|
||||||
const auto event = m_room->getEvent(m_eventId);
|
const auto event = m_room->getEvent(m_eventId);
|
||||||
if (event.first == nullptr || m_isReply) {
|
if (event == nullptr || m_isReply) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
if (!EventHandler::hasReply(event) || (EventHandler::isThreaded(event) && NeoChatConfig::self()->threads())) {
|
||||||
if (roomMessageEvent == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!roomMessageEvent->isReply() || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) {
|
|
||||||
if (m_replyModel) {
|
if (m_replyModel) {
|
||||||
delete m_replyModel;
|
delete m_replyModel;
|
||||||
}
|
}
|
||||||
@@ -513,7 +488,7 @@ void MessageContentModel::updateReplyModel()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(), true, false, this);
|
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event), true, false, this);
|
||||||
|
|
||||||
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
|
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
|
||||||
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
|
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
|
||||||
@@ -523,13 +498,13 @@ void MessageContentModel::updateReplyModel()
|
|||||||
QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentType::Type type)
|
QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentType::Type type)
|
||||||
{
|
{
|
||||||
const auto event = m_room->getEvent(m_eventId);
|
const auto event = m_room->getEvent(m_eventId);
|
||||||
if (event.first == nullptr) {
|
if (event == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MessageComponentType::Text: {
|
case MessageComponentType::Text: {
|
||||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||||
return TextHandler().textComponents(body,
|
return TextHandler().textComponents(body,
|
||||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||||
@@ -540,11 +515,11 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
|||||||
case MessageComponentType::File: {
|
case MessageComponentType::File: {
|
||||||
QList<MessageComponent> components;
|
QList<MessageComponent> components;
|
||||||
components += MessageComponent{MessageComponentType::File, QString(), {}};
|
components += MessageComponent{MessageComponentType::File, QString(), {}};
|
||||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||||
|
|
||||||
if (m_emptyItinerary) {
|
if (m_emptyItinerary) {
|
||||||
if (!m_isReply) {
|
if (!m_isReply) {
|
||||||
auto fileTransferInfo = m_room->cachedFileTransferInfo(event.first);
|
auto fileTransferInfo = m_room->cachedFileTransferInfo(event);
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->has<EventContent::FileContent>());
|
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->has<EventContent::FileContent>());
|
||||||
@@ -592,8 +567,8 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
|||||||
case MessageComponentType::Image:
|
case MessageComponentType::Image:
|
||||||
case MessageComponentType::Audio:
|
case MessageComponentType::Audio:
|
||||||
case MessageComponentType::Video: {
|
case MessageComponentType::Video: {
|
||||||
if (!event.first->is<StickerEvent>()) {
|
if (!event->is<StickerEvent>()) {
|
||||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
|
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||||
const auto fileContent = roomMessageEvent->get<EventContent::FileContentBase>();
|
const auto fileContent = roomMessageEvent->get<EventContent::FileContentBase>();
|
||||||
if (fileContent != nullptr) {
|
if (fileContent != nullptr) {
|
||||||
const auto fileInfo = fileContent->commonInfo();
|
const auto fileInfo = fileContent->commonInfo();
|
||||||
@@ -612,7 +587,6 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[[fallthrough]];
|
|
||||||
default:
|
default:
|
||||||
return {MessageComponent{type, QString(), {}}};
|
return {MessageComponent{type, QString(), {}}};
|
||||||
}
|
}
|
||||||
@@ -686,13 +660,13 @@ void MessageContentModel::closeLinkPreview(int row)
|
|||||||
void MessageContentModel::updateItineraryModel()
|
void MessageContentModel::updateItineraryModel()
|
||||||
{
|
{
|
||||||
const auto event = m_room->getEvent(m_eventId);
|
const auto event = m_room->getEvent(m_eventId);
|
||||||
if (m_room == nullptr || event.first == nullptr) {
|
if (m_room == nullptr || event == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first)) {
|
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||||
if (roomMessageEvent->has<EventContent::FileContent>()) {
|
if (roomMessageEvent->has<EventContent::FileContent>()) {
|
||||||
auto filePath = m_room->cachedFileTransferInfo(event.first).localPath;
|
auto filePath = m_room->cachedFileTransferInfo(event).localPath;
|
||||||
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
|
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
|
||||||
delete m_itineraryModel;
|
delete m_itineraryModel;
|
||||||
m_itineraryModel = nullptr;
|
m_itineraryModel = nullptr;
|
||||||
|
|||||||
@@ -31,14 +31,6 @@ class MessageContentModel : public QAbstractListModel
|
|||||||
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
|
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
|
||||||
|
|
||||||
public:
|
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.
|
* @brief Defines the model roles.
|
||||||
*/
|
*/
|
||||||
@@ -106,6 +98,7 @@ public:
|
|||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void showAuthorChanged();
|
void showAuthorChanged();
|
||||||
|
void eventUnavailable();
|
||||||
void eventUpdated();
|
void eventUpdated();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -114,9 +107,10 @@ private:
|
|||||||
QString m_eventSenderId;
|
QString m_eventSenderId;
|
||||||
std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr;
|
std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr;
|
||||||
|
|
||||||
MessageState m_currentState = Unknown;
|
bool m_isPending;
|
||||||
bool m_showAuthor = true;
|
bool m_showAuthor = true;
|
||||||
bool m_isReply;
|
bool m_isReply;
|
||||||
|
bool m_notFound = false;
|
||||||
|
|
||||||
void initializeModel();
|
void initializeModel();
|
||||||
void initializeEvent();
|
void initializeEvent();
|
||||||
|
|||||||
@@ -160,21 +160,12 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
refreshLastUserEvents(i);
|
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) {
|
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
createEventObjects(event, true);
|
createEventObjects(event);
|
||||||
beginInsertRows({}, 0, 0);
|
beginInsertRows({}, 0, 0);
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
||||||
#endif
|
|
||||||
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent *, int i) {
|
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent *, int i) {
|
||||||
Q_EMIT dataChanged(index(i, 0), index(i, 0), {IsPendingRole});
|
Q_EMIT dataChanged(index(i, 0), index(i, 0), {IsPendingRole});
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
@@ -501,8 +492,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return EventStatus::Hidden;
|
return EventStatus::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt);
|
if (EventHandler::isThreaded(&evt) && EventHandler::threadRoot(&evt) != EventHandler::id(&evt) && NeoChatConfig::threads()) {
|
||||||
if (roomMessageEvent && roomMessageEvent->isThreaded() && roomMessageEvent->threadRootEventId() != evt.id() && NeoChatConfig::threads()) {
|
|
||||||
return EventStatus::Hidden;
|
return EventStatus::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +500,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == EventIdRole) {
|
if (role == EventIdRole) {
|
||||||
return evt.displayId();
|
return EventHandler::id(&evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == ProgressInfoRole) {
|
if (role == ProgressInfoRole) {
|
||||||
@@ -535,18 +525,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == IsThreadedRole) {
|
if (role == IsThreadedRole) {
|
||||||
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt)) {
|
return EventHandler::isThreaded(&evt);
|
||||||
return roomMessageEvent->isThreaded();
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == ThreadRootRole) {
|
if (role == ThreadRootRole) {
|
||||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt);
|
return EventHandler::threadRoot(&evt);
|
||||||
if (roomMessageEvent && roomMessageEvent->isThreaded()) {
|
|
||||||
return roomMessageEvent->threadRootEventId();
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == ShowSectionRole) {
|
if (role == ShowSectionRole) {
|
||||||
@@ -635,7 +618,7 @@ int MessageEventModel::eventIdToRow(const QString &eventID) const
|
|||||||
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
|
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event, bool isPending)
|
void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
|
||||||
{
|
{
|
||||||
if (event == nullptr) {
|
if (event == nullptr) {
|
||||||
return;
|
return;
|
||||||
@@ -658,14 +641,12 @@ void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event, boo
|
|||||||
|
|
||||||
if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) {
|
if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) {
|
||||||
if (!event->isStateEvent() || event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
if (!event->isStateEvent() || event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||||
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, eventId, false, isPending));
|
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, eventId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
if (EventHandler::isThreaded(event) && !m_threadModels.contains(EventHandler::threadRoot(event))) {
|
||||||
if (roomMessageEvent && roomMessageEvent->isThreaded() && !m_threadModels.contains(roomMessageEvent->threadRootEventId())) {
|
m_threadModels[EventHandler::threadRoot(event)] = QSharedPointer<ThreadModel>(new ThreadModel(EventHandler::threadRoot(event), m_currentRoom));
|
||||||
m_threadModels[roomMessageEvent->threadRootEventId()] =
|
|
||||||
QSharedPointer<ThreadModel>(new ThreadModel(roomMessageEvent->threadRootEventId(), m_currentRoom));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadMarkerModel handles updates to add and remove markers, we only need to
|
// ReadMarkerModel handles updates to add and remove markers, we only need to
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ private:
|
|||||||
int refreshEventRoles(const QString &eventId, const QList<int> &roles = {});
|
int refreshEventRoles(const QString &eventId, const QList<int> &roles = {});
|
||||||
void moveReadMarker(const QString &toEventId);
|
void moveReadMarker(const QString &toEventId);
|
||||||
|
|
||||||
void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false);
|
void createEventObjects(const Quotient::RoomEvent *event);
|
||||||
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
|
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
|
|
||||||
|
|||||||
@@ -8,23 +8,6 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
class NeoChatQueryPublicRoomsJob : public QueryPublicRoomsJob
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit NeoChatQueryPublicRoomsJob(const QString &server = {},
|
|
||||||
std::optional<int> limit = std::nullopt,
|
|
||||||
const QString &since = {},
|
|
||||||
const std::optional<Filter> &filter = std::nullopt,
|
|
||||||
std::optional<bool> includeAllNetworks = std::nullopt,
|
|
||||||
const QString &thirdPartyInstanceId = {})
|
|
||||||
: QueryPublicRoomsJob(server, limit, since, filter, includeAllNetworks, thirdPartyInstanceId)
|
|
||||||
{
|
|
||||||
// TODO Remove once we can use libQuotient's job directly
|
|
||||||
// This is to make libQuotient happy about results not having the "chunk" field
|
|
||||||
setExpectedKeys({});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PublicRoomListModel::PublicRoomListModel(QObject *parent)
|
PublicRoomListModel::PublicRoomListModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
@@ -170,8 +153,6 @@ void PublicRoomListModel::next(int limit)
|
|||||||
if (m_connection == nullptr || limit < 1) {
|
if (m_connection == nullptr || limit < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_redirectedText.clear();
|
|
||||||
Q_EMIT redirectedChanged();
|
|
||||||
|
|
||||||
if (job) {
|
if (job) {
|
||||||
qCDebug(PublicRoomList) << "Other job running, ignore";
|
qCDebug(PublicRoomList) << "Other job running, ignore";
|
||||||
@@ -182,7 +163,7 @@ void PublicRoomListModel::next(int limit)
|
|||||||
if (m_showOnlySpaces) {
|
if (m_showOnlySpaces) {
|
||||||
roomTypes += QLatin1String("m.space");
|
roomTypes += QLatin1String("m.space");
|
||||||
}
|
}
|
||||||
job = m_connection->callApi<NeoChatQueryPublicRoomsJob>(m_server, limit, nextBatch, QueryPublicRoomsJob::Filter{m_searchText, roomTypes});
|
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, limit, nextBatch, QueryPublicRoomsJob::Filter{m_searchText, roomTypes});
|
||||||
Q_EMIT searchingChanged();
|
Q_EMIT searchingChanged();
|
||||||
|
|
||||||
connect(job, &BaseJob::finished, this, [this] {
|
connect(job, &BaseJob::finished, this, [this] {
|
||||||
@@ -200,9 +181,6 @@ void PublicRoomListModel::next(int limit)
|
|||||||
this->beginInsertRows({}, rooms.count(), rooms.count() + job->chunk().count() - 1);
|
this->beginInsertRows({}, rooms.count(), rooms.count() + job->chunk().count() - 1);
|
||||||
rooms.append(job->chunk());
|
rooms.append(job->chunk());
|
||||||
this->endInsertRows();
|
this->endInsertRows();
|
||||||
} else if (job->error() == BaseJob::ContentAccessError) {
|
|
||||||
m_redirectedText = job->jsonData()[u"error"_s].toString();
|
|
||||||
Q_EMIT redirectedChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->job = nullptr;
|
this->job = nullptr;
|
||||||
@@ -324,9 +302,4 @@ bool PublicRoomListModel::searching() const
|
|||||||
return job != nullptr;
|
return job != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PublicRoomListModel::redirectedText() const
|
|
||||||
{
|
|
||||||
return m_redirectedText;
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "moc_publicroomlistmodel.cpp"
|
#include "moc_publicroomlistmodel.cpp"
|
||||||
|
|||||||
@@ -52,11 +52,6 @@ class PublicRoomListModel : public QAbstractListModel
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
|
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The text returned by the server after redirection
|
|
||||||
*/
|
|
||||||
Q_PROPERTY(QString redirectedText READ redirectedText NOTIFY redirectedChanged)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Defines the model roles.
|
* @brief Defines the model roles.
|
||||||
@@ -118,8 +113,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
Q_INVOKABLE void search(int limit = 50);
|
Q_INVOKABLE void search(int limit = 50);
|
||||||
|
|
||||||
QString redirectedText() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<NeoChatConnection> m_connection = nullptr;
|
QPointer<NeoChatConnection> m_connection = nullptr;
|
||||||
QString m_server;
|
QString m_server;
|
||||||
@@ -142,7 +135,6 @@ private:
|
|||||||
QList<Quotient::PublicRoomsChunk> rooms;
|
QList<Quotient::PublicRoomsChunk> rooms;
|
||||||
|
|
||||||
Quotient::QueryPublicRoomsJob *job = nullptr;
|
Quotient::QueryPublicRoomsJob *job = nullptr;
|
||||||
QString m_redirectedText;
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
@@ -150,5 +142,4 @@ Q_SIGNALS:
|
|||||||
void searchTextChanged();
|
void searchTextChanged();
|
||||||
void showOnlySpacesChanged();
|
void showOnlySpacesChanged();
|
||||||
void searchingChanged();
|
void searchingChanged();
|
||||||
void redirectedChanged();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
|||||||
refresh(room);
|
refresh(room);
|
||||||
});
|
});
|
||||||
connect(room, &Room::addedMessages, this, [this, room] {
|
connect(room, &Room::addedMessages, this, [this, room] {
|
||||||
refresh(room, {SubtitleTextRole});
|
refresh(room, {SubtitleTextRole, LastActiveTimeRole});
|
||||||
});
|
});
|
||||||
connect(room, &Room::pendingEventMerged, this, [this, room] {
|
connect(room, &Room::pendingEventMerged, this, [this, room] {
|
||||||
refresh(room, {SubtitleTextRole});
|
refresh(room, {SubtitleTextRole});
|
||||||
@@ -229,6 +229,9 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
if (role == HasHighlightNotificationsRole) {
|
if (role == HasHighlightNotificationsRole) {
|
||||||
return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
|
return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
|
||||||
}
|
}
|
||||||
|
if (role == LastActiveTimeRole) {
|
||||||
|
return room->lastActiveTime();
|
||||||
|
}
|
||||||
if (role == JoinStateRole) {
|
if (role == JoinStateRole) {
|
||||||
if (!room->successorId().isEmpty()) {
|
if (!room->successorId().isEmpty()) {
|
||||||
return QStringLiteral("upgraded");
|
return QStringLiteral("upgraded");
|
||||||
@@ -288,6 +291,7 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
|
|||||||
roles[CategoryRole] = "category";
|
roles[CategoryRole] = "category";
|
||||||
roles[ContextNotificationCountRole] = "contextNotificationCount";
|
roles[ContextNotificationCountRole] = "contextNotificationCount";
|
||||||
roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
|
roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
|
||||||
|
roles[LastActiveTimeRole] = "lastActiveTime";
|
||||||
roles[JoinStateRole] = "joinState";
|
roles[JoinStateRole] = "joinState";
|
||||||
roles[CurrentRoomRole] = "currentRoom";
|
roles[CurrentRoomRole] = "currentRoom";
|
||||||
roles[SubtitleTextRole] = "subtitleText";
|
roles[SubtitleTextRole] = "subtitleText";
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public:
|
|||||||
CategoryRole, /**< The room category, e.g favourite. */
|
CategoryRole, /**< The room category, e.g favourite. */
|
||||||
ContextNotificationCountRole, /**< The context aware notification count for the room. */
|
ContextNotificationCountRole, /**< The context aware notification count for the room. */
|
||||||
HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
|
HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
|
||||||
|
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
|
||||||
JoinStateRole, /**< The local user's join state in the room. */
|
JoinStateRole, /**< The local user's join state in the room. */
|
||||||
CurrentRoomRole, /**< The room object for the room. */
|
CurrentRoomRole, /**< The room object for the room. */
|
||||||
SubtitleTextRole, /**< The text to show as the room subtitle. */
|
SubtitleTextRole, /**< The text to show as the room subtitle. */
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
|
|||||||
refreshRoomRoles(room);
|
refreshRoomRoles(room);
|
||||||
});
|
});
|
||||||
connect(room, &Room::addedMessages, this, [this, room] {
|
connect(room, &Room::addedMessages, this, [this, room] {
|
||||||
refreshRoomRoles(room, {SubtitleTextRole});
|
refreshRoomRoles(room, {SubtitleTextRole, LastActiveTimeRole});
|
||||||
});
|
});
|
||||||
connect(room, &Room::pendingEventMerged, this, [this, room] {
|
connect(room, &Room::pendingEventMerged, this, [this, room] {
|
||||||
refreshRoomRoles(room, {SubtitleTextRole});
|
refreshRoomRoles(room, {SubtitleTextRole});
|
||||||
@@ -274,6 +274,7 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
|
|||||||
roles[CategoryRole] = "category";
|
roles[CategoryRole] = "category";
|
||||||
roles[ContextNotificationCountRole] = "contextNotificationCount";
|
roles[ContextNotificationCountRole] = "contextNotificationCount";
|
||||||
roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
|
roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
|
||||||
|
roles[LastActiveTimeRole] = "lastActiveTime";
|
||||||
roles[JoinStateRole] = "joinState";
|
roles[JoinStateRole] = "joinState";
|
||||||
roles[CurrentRoomRole] = "currentRoom";
|
roles[CurrentRoomRole] = "currentRoom";
|
||||||
roles[SubtitleTextRole] = "subtitleText";
|
roles[SubtitleTextRole] = "subtitleText";
|
||||||
@@ -283,6 +284,8 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
|
|||||||
roles[IsDirectChat] = "isDirectChat";
|
roles[IsDirectChat] = "isDirectChat";
|
||||||
roles[DelegateTypeRole] = "delegateType";
|
roles[DelegateTypeRole] = "delegateType";
|
||||||
roles[IconRole] = "icon";
|
roles[IconRole] = "icon";
|
||||||
|
roles[AttentionRole] = "attention";
|
||||||
|
roles[FavouriteRole] = "favourite";
|
||||||
roles[RoomTypeRole] = "roomType";
|
roles[RoomTypeRole] = "roomType";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
@@ -338,6 +341,9 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
|||||||
if (role == HasHighlightNotificationsRole) {
|
if (role == HasHighlightNotificationsRole) {
|
||||||
return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
|
return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
|
||||||
}
|
}
|
||||||
|
if (role == LastActiveTimeRole) {
|
||||||
|
return room->lastActiveTime();
|
||||||
|
}
|
||||||
if (role == JoinStateRole) {
|
if (role == JoinStateRole) {
|
||||||
if (!room->successorId().isEmpty()) {
|
if (!room->successorId().isEmpty()) {
|
||||||
return QStringLiteral("upgraded");
|
return QStringLiteral("upgraded");
|
||||||
@@ -374,6 +380,12 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
|||||||
if (role == DelegateTypeRole) {
|
if (role == DelegateTypeRole) {
|
||||||
return QStringLiteral("normal");
|
return QStringLiteral("normal");
|
||||||
}
|
}
|
||||||
|
if (role == AttentionRole) {
|
||||||
|
return room->notificationCount() + room->highlightCount() > 0;
|
||||||
|
}
|
||||||
|
if (role == FavouriteRole) {
|
||||||
|
return room->isFavourite();
|
||||||
|
}
|
||||||
if (role == RoomTypeRole) {
|
if (role == RoomTypeRole) {
|
||||||
if (room->creation()) {
|
if (room->creation()) {
|
||||||
return room->creation()->contentPart<QString>("type"_L1);
|
return room->creation()->contentPart<QString>("type"_L1);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public:
|
|||||||
CategoryRole, /**< The room category, e.g favourite. */
|
CategoryRole, /**< The room category, e.g favourite. */
|
||||||
ContextNotificationCountRole, /**< The context aware notification count for the room. */
|
ContextNotificationCountRole, /**< The context aware notification count for the room. */
|
||||||
HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
|
HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
|
||||||
|
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
|
||||||
JoinStateRole, /**< The local user's join state in the room. */
|
JoinStateRole, /**< The local user's join state in the room. */
|
||||||
CurrentRoomRole, /**< The room object for the room. */
|
CurrentRoomRole, /**< The room object for the room. */
|
||||||
SubtitleTextRole, /**< The text to show as the room subtitle. */
|
SubtitleTextRole, /**< The text to show as the room subtitle. */
|
||||||
@@ -47,6 +48,8 @@ public:
|
|||||||
IsDirectChat, /**< Whether this room is a direct chat. */
|
IsDirectChat, /**< Whether this room is a direct chat. */
|
||||||
DelegateTypeRole,
|
DelegateTypeRole,
|
||||||
IconRole,
|
IconRole,
|
||||||
|
AttentionRole, /**< Whether there are any notifications. */
|
||||||
|
FavouriteRole, /**< Whether the room is favourited. */
|
||||||
RoomTypeRole, /**< The room's type. */
|
RoomTypeRole, /**< The room's type. */
|
||||||
};
|
};
|
||||||
Q_ENUM(EventRoles)
|
Q_ENUM(EventRoles)
|
||||||
|
|||||||
@@ -108,17 +108,11 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
|||||||
case HighlightRole:
|
case HighlightRole:
|
||||||
return EventHandler::isHighlighted(m_room, &event);
|
return EventHandler::isHighlighted(m_room, &event);
|
||||||
case EventIdRole:
|
case EventIdRole:
|
||||||
return event.displayId();
|
return EventHandler::id(&event);
|
||||||
case IsThreadedRole:
|
case IsThreadedRole:
|
||||||
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event)) {
|
return EventHandler::isThreaded(&event);
|
||||||
return roomMessageEvent->isThreaded();
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
case ThreadRootRole:
|
case ThreadRootRole:
|
||||||
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event); roomMessageEvent->isThreaded()) {
|
return EventHandler::threadRoot(&event);
|
||||||
return roomMessageEvent->threadRootEventId();
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
case ContentModelRole: {
|
case ContentModelRole: {
|
||||||
if (!event.isStateEvent()) {
|
if (!event.isStateEvent()) {
|
||||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, event.id()));
|
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, event.id()));
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
#include "neochatroom.h"
|
|
||||||
#include "neochatroomtype.h"
|
#include "neochatroomtype.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "roomtreemodel.h"
|
#include "roomtreemodel.h"
|
||||||
@@ -45,49 +44,58 @@ SortFilterRoomTreeModel::SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QOb
|
|||||||
void SortFilterRoomTreeModel::setRoomSortOrder(SortFilterRoomTreeModel::RoomSortOrder sortOrder)
|
void SortFilterRoomTreeModel::setRoomSortOrder(SortFilterRoomTreeModel::RoomSortOrder sortOrder)
|
||||||
{
|
{
|
||||||
m_sortOrder = sortOrder;
|
m_sortOrder = sortOrder;
|
||||||
|
if (sortOrder == SortFilterRoomTreeModel::Alphabetical) {
|
||||||
|
setSortRole(RoomTreeModel::DisplayNameRole);
|
||||||
|
} else if (sortOrder == SortFilterRoomTreeModel::Activity) {
|
||||||
|
setSortRole(RoomTreeModel::LastActiveTimeRole);
|
||||||
|
}
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
static const QVector<RoomSortParameter::Parameter> alphabeticalSortPriorities{
|
static const QVector<RoomTreeModel::EventRoles> alphabeticalSortPriorities{
|
||||||
// Does exactly what it says on the tin.
|
// Does exactly what it says on the tin.
|
||||||
RoomSortParameter::AlphabeticalAscending,
|
RoomTreeModel::DisplayNameRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const QVector<RoomSortParameter::Parameter> activitySortPriorities{
|
static const QVector<RoomTreeModel::EventRoles> activitySortPriorities{
|
||||||
RoomSortParameter::HasHighlight,
|
// Anything useful at the top, quiet rooms at the bottom
|
||||||
RoomSortParameter::MostHighlights,
|
RoomTreeModel::AttentionRole,
|
||||||
RoomSortParameter::HasUnread,
|
// Organize by highlights, notifications, unread favorites, all other unread, in that order
|
||||||
RoomSortParameter::MostUnread,
|
RoomTreeModel::HasHighlightNotificationsRole,
|
||||||
RoomSortParameter::LastActive,
|
RoomTreeModel::ContextNotificationCountRole,
|
||||||
|
RoomTreeModel::FavouriteRole,
|
||||||
|
// Finally sort by last activity time
|
||||||
|
RoomTreeModel::LastActiveTimeRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const QVector<RoomSortParameter::Parameter> lastMessageSortPriorities{
|
bool SortFilterRoomTreeModel::roleCmp(const QVariant &sortLeft, const QVariant &sortRight) const
|
||||||
RoomSortParameter::LastActive,
|
{
|
||||||
};
|
switch (sortLeft.typeId()) {
|
||||||
|
case QMetaType::Bool:
|
||||||
|
return (sortLeft == sortRight) ? false : sortLeft.toBool();
|
||||||
|
case QMetaType::QString:
|
||||||
|
return sortLeft.toString() < sortRight.toString();
|
||||||
|
case QMetaType::Int:
|
||||||
|
return sortLeft.toInt() > sortRight.toInt();
|
||||||
|
case QMetaType::QDateTime:
|
||||||
|
return sortLeft.toDateTime() > sortRight.toDateTime();
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SortFilterRoomTreeModel::prioritiesCmp(const QVector<RoomSortParameter::Parameter> &priorities,
|
bool SortFilterRoomTreeModel::prioritiesCmp(const QVector<RoomTreeModel::EventRoles> &priorities,
|
||||||
const QModelIndex &source_left,
|
const QModelIndex &source_left,
|
||||||
const QModelIndex &source_right) const
|
const QModelIndex &source_right) const
|
||||||
{
|
{
|
||||||
const auto treeModel = dynamic_cast<RoomTreeModel *>(sourceModel());
|
for (RoomTreeModel::EventRoles sortRole : priorities) {
|
||||||
if (treeModel == nullptr) {
|
const auto sortLeft = sourceModel()->data(source_left, sortRole);
|
||||||
return false;
|
const auto sortRight = sourceModel()->data(source_right, sortRole);
|
||||||
}
|
if (sortLeft != sortRight) {
|
||||||
|
return roleCmp(sortLeft, sortRight);
|
||||||
const auto leftRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_left.data(RoomTreeModel::RoomIdRole).toString()));
|
|
||||||
const auto rightRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_right.data(RoomTreeModel::RoomIdRole).toString()));
|
|
||||||
if (leftRoom == nullptr || rightRoom == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto sortRole : priorities) {
|
|
||||||
auto result = RoomSortParameter::compareParameter(sortRole, leftRoom, rightRoom);
|
|
||||||
|
|
||||||
if (result != 0) {
|
|
||||||
return result > 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
||||||
@@ -102,9 +110,8 @@ bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QMo
|
|||||||
return prioritiesCmp(alphabeticalSortPriorities, source_left, source_right);
|
return prioritiesCmp(alphabeticalSortPriorities, source_left, source_right);
|
||||||
case SortFilterRoomTreeModel::Activity:
|
case SortFilterRoomTreeModel::Activity:
|
||||||
return prioritiesCmp(activitySortPriorities, source_left, source_right);
|
return prioritiesCmp(activitySortPriorities, source_left, source_right);
|
||||||
case SortFilterRoomTreeModel::LastMessage:
|
|
||||||
return prioritiesCmp(lastMessageSortPriorities, source_left, source_right);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
#include "enums/roomsortparameter.h"
|
|
||||||
#include "models/roomtreemodel.h"
|
#include "models/roomtreemodel.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,7 +53,6 @@ public:
|
|||||||
enum RoomSortOrder {
|
enum RoomSortOrder {
|
||||||
Alphabetical,
|
Alphabetical,
|
||||||
Activity,
|
Activity,
|
||||||
LastMessage,
|
|
||||||
};
|
};
|
||||||
Q_ENUM(RoomSortOrder)
|
Q_ENUM(RoomSortOrder)
|
||||||
|
|
||||||
@@ -106,5 +104,6 @@ private:
|
|||||||
QString m_filterText;
|
QString m_filterText;
|
||||||
QString m_activeSpaceId;
|
QString m_activeSpaceId;
|
||||||
|
|
||||||
bool prioritiesCmp(const QVector<RoomSortParameter::Parameter> &priorities, const QModelIndex &left, const QModelIndex &right) const;
|
bool roleCmp(const QVariant &left, const QVariant &right) const;
|
||||||
|
bool prioritiesCmp(const QVector<RoomTreeModel::EventRoles> &priorities, const QModelIndex &left, const QModelIndex &right) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
|
|||||||
|
|
||||||
connect(room, &Quotient::Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
connect(room, &Quotient::Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
||||||
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||||
if (roomEvent->isThreaded() && roomEvent->threadRootEventId() == m_threadRootId) {
|
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
|
||||||
addNewEvent(event);
|
addNewEvent(event);
|
||||||
addModels();
|
addModels();
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
|
|||||||
connect(room, &Quotient::Room::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
|
connect(room, &Quotient::Room::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
|
||||||
for (const auto &event : events) {
|
for (const auto &event : events) {
|
||||||
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||||
if (roomEvent->isThreaded() && roomEvent->threadRootEventId() == m_threadRootId) {
|
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
|
||||||
addNewEvent(roomEvent);
|
addNewEvent(roomEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
|
|
||||||
#include <Quotient/events/eventcontent.h>
|
#include <Quotient/events/eventcontent.h>
|
||||||
#include <Quotient/events/eventrelation.h>
|
|
||||||
#include <Quotient/events/roommessageevent.h>
|
|
||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
#include <Quotient/quotient_common.h>
|
#include <Quotient/quotient_common.h>
|
||||||
#include <qcoro/qcorosignal.h>
|
#include <qcoro/qcorosignal.h>
|
||||||
@@ -484,6 +482,119 @@ QString msgTypeToString(MessageEventType msgType)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::postMessage(const QString &rawText,
|
||||||
|
const QString &text,
|
||||||
|
MessageEventType type,
|
||||||
|
const QString &replyEventId,
|
||||||
|
const QString &relateToEventId,
|
||||||
|
const QString &threadRootId,
|
||||||
|
const QString &fallbackId)
|
||||||
|
{
|
||||||
|
postHtmlMessage(rawText, text, type, replyEventId, relateToEventId, threadRootId, fallbackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::postHtmlMessage(const QString &text,
|
||||||
|
const QString &html,
|
||||||
|
MessageEventType type,
|
||||||
|
const QString &replyEventId,
|
||||||
|
const QString &relateToEventId,
|
||||||
|
const QString &threadRootId,
|
||||||
|
const QString &fallbackId)
|
||||||
|
{
|
||||||
|
bool isReply = !replyEventId.isEmpty();
|
||||||
|
bool isEdit = !relateToEventId.isEmpty();
|
||||||
|
bool isThread = !threadRootId.isEmpty();
|
||||||
|
const auto replyIt = findInTimeline(replyEventId);
|
||||||
|
if (replyIt == historyEdge()) {
|
||||||
|
isReply = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isThread) {
|
||||||
|
bool isFallingBack = !fallbackId.isEmpty();
|
||||||
|
QString replyEventId = isFallingBack ? fallbackId : QString();
|
||||||
|
if (isReply) {
|
||||||
|
isFallingBack = false;
|
||||||
|
replyEventId = EventHandler::id(replyIt->get());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are not replying and there is no fallback ID it means a new thread
|
||||||
|
// is being created.
|
||||||
|
if (!isFallingBack && !isReply) {
|
||||||
|
isFallingBack = true;
|
||||||
|
replyEventId = threadRootId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
QJsonObject json{
|
||||||
|
{"msgtype"_ls, msgTypeToString(type)},
|
||||||
|
{"body"_ls, text},
|
||||||
|
{"format"_ls, "org.matrix.custom.html"_ls},
|
||||||
|
{"m.relates_to"_ls,
|
||||||
|
QJsonObject {
|
||||||
|
{"rel_type"_ls, "m.thread"_ls},
|
||||||
|
{"event_id"_ls, threadRootId},
|
||||||
|
{"is_falling_back"_ls, isFallingBack},
|
||||||
|
{"m.in_reply_to"_ls,
|
||||||
|
QJsonObject {
|
||||||
|
{"event_id"_ls, replyEventId}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"formatted_body"_ls, html}
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
postJson("m.room.message"_ls, json);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEdit) {
|
||||||
|
QJsonObject json{
|
||||||
|
{"type"_ls, "m.room.message"_ls},
|
||||||
|
{"msgtype"_ls, msgTypeToString(type)},
|
||||||
|
{"body"_ls, "* %1"_ls.arg(text)},
|
||||||
|
{"format"_ls, "org.matrix.custom.html"_ls},
|
||||||
|
{"formatted_body"_ls, html},
|
||||||
|
{"m.new_content"_ls,
|
||||||
|
QJsonObject{{"body"_ls, text}, {"msgtype"_ls, msgTypeToString(type)}, {"format"_ls, "org.matrix.custom.html"_ls}, {"formatted_body"_ls, html}}},
|
||||||
|
{"m.relates_to"_ls, QJsonObject{{"rel_type"_ls, "m.replace"_ls}, {"event_id"_ls, relateToEventId}}}};
|
||||||
|
|
||||||
|
postJson("m.room.message"_ls, json);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReply) {
|
||||||
|
const auto &replyEvt = **replyIt;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
QJsonObject json{
|
||||||
|
{"msgtype"_ls, msgTypeToString(type)},
|
||||||
|
{"body"_ls, "> <%1> %2\n\n%3"_ls.arg(replyEvt.senderId(), EventHandler::plainBody(this, &replyEvt), text)},
|
||||||
|
{"format"_ls, "org.matrix.custom.html"_ls},
|
||||||
|
{"m.relates_to"_ls,
|
||||||
|
QJsonObject {
|
||||||
|
{"m.in_reply_to"_ls,
|
||||||
|
QJsonObject {
|
||||||
|
{"event_id"_ls, replyEventId}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"formatted_body"_ls,
|
||||||
|
"<mx-reply><blockquote><a href=\"https://matrix.to/#/%1/%2\">In reply to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br>%5</blockquote></mx-reply>%6"_ls.arg(id(), replyEventId, replyEvt.senderId(), replyEvt.senderId(), EventHandler::richBody(this, &replyEvt), html)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
postJson("m.room.message"_ls, json);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Room::postHtmlMessage(text, html, type);
|
||||||
|
}
|
||||||
|
|
||||||
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
|
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
|
||||||
{
|
{
|
||||||
if (eventId.isEmpty() || reaction.isEmpty()) {
|
if (eventId.isEmpty() || reaction.isEmpty()) {
|
||||||
@@ -1638,31 +1749,25 @@ void NeoChatRoom::downloadEventFromServer(const QString &eventId)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<const Quotient::RoomEvent *, bool> NeoChatRoom::getEvent(const QString &eventId) const
|
const RoomEvent *NeoChatRoom::getEvent(const QString &eventId) const
|
||||||
{
|
{
|
||||||
if (eventId.isEmpty()) {
|
if (eventId.isEmpty()) {
|
||||||
return {};
|
return nullptr;
|
||||||
}
|
}
|
||||||
const auto timelineIt = findInTimeline(eventId);
|
const auto timelineIt = findInTimeline(eventId);
|
||||||
if (timelineIt != historyEdge()) {
|
if (timelineIt != historyEdge()) {
|
||||||
return std::make_pair(timelineIt->get(), false);
|
return timelineIt->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pendingIt = findPendingEvent(eventId);
|
const auto pendingIt = findPendingEvent(eventId);
|
||||||
if (pendingIt != pendingEvents().end()) {
|
if (pendingIt != pendingEvents().end()) {
|
||||||
return std::make_pair(pendingIt->event(), true);
|
return pendingIt->event();
|
||||||
}
|
|
||||||
// findPendingEvent() searches by transaction ID, we also need to check event ID.
|
|
||||||
for (const auto &event : pendingEvents()) {
|
|
||||||
if (event->id() == eventId || event->transactionId() == eventId) {
|
|
||||||
return std::make_pair(event.event(), true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto extraIt = std::find_if(m_extraEvents.begin(), m_extraEvents.end(), [eventId](const Quotient::event_ptr_tt<Quotient::RoomEvent> &event) {
|
auto extraIt = std::find_if(m_extraEvents.begin(), m_extraEvents.end(), [eventId](const Quotient::event_ptr_tt<Quotient::RoomEvent> &event) {
|
||||||
return event->id() == eventId;
|
return event->id() == eventId;
|
||||||
});
|
});
|
||||||
return std::make_pair(extraIt != m_extraEvents.end() ? extraIt->get() : nullptr, false);
|
return extraIt != m_extraEvents.end() ? extraIt->get() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomEvent *NeoChatRoom::getReplyForEvent(const RoomEvent &event) const
|
const RoomEvent *NeoChatRoom::getReplyForEvent(const RoomEvent &event) const
|
||||||
|
|||||||
@@ -570,7 +570,7 @@ public:
|
|||||||
*
|
*
|
||||||
* The result will be nullptr if not found so needs to be managed.
|
* The result will be nullptr if not found so needs to be managed.
|
||||||
*/
|
*/
|
||||||
std::pair<const Quotient::RoomEvent *, bool> getEvent(const QString &eventId) const;
|
const Quotient::RoomEvent *getEvent(const QString &eventId) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the event that is being replied to. This includes events that were manually loaded using NeoChatRoom::loadReply.
|
* @brief Returns the event that is being replied to. This includes events that were manually loaded using NeoChatRoom::loadReply.
|
||||||
@@ -691,6 +691,40 @@ public Q_SLOTS:
|
|||||||
*/
|
*/
|
||||||
void sendTypingNotification(bool isTyping);
|
void sendTypingNotification(bool isTyping);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a message to the room.
|
||||||
|
*
|
||||||
|
* @param rawText the text as it was typed.
|
||||||
|
* @param cleanedText the text marked up as html.
|
||||||
|
* @param type the type of message being sent.
|
||||||
|
* @param replyEventId the id of the message being replied to if a reply.
|
||||||
|
* @param relateToEventId the id of the message being edited if an edit.
|
||||||
|
*/
|
||||||
|
void postMessage(const QString &rawText,
|
||||||
|
const QString &cleanedText,
|
||||||
|
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
||||||
|
const QString &replyEventId = QString(),
|
||||||
|
const QString &relateToEventId = QString(),
|
||||||
|
const QString &threadRootId = QString(),
|
||||||
|
const QString &fallbackId = QString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send an html message to the room.
|
||||||
|
*
|
||||||
|
* @param text the text as it was typed.
|
||||||
|
* @param html the text marked up as html.
|
||||||
|
* @param type the type of message being sent.
|
||||||
|
* @param replyEventId the id of the message being replied to if a reply.
|
||||||
|
* @param relateToEventId the id of the message being edited if an edit.
|
||||||
|
*/
|
||||||
|
void postHtmlMessage(const QString &text,
|
||||||
|
const QString &html,
|
||||||
|
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
||||||
|
const QString &replyEventId = QString(),
|
||||||
|
const QString &relateToEventId = QString(),
|
||||||
|
const QString &threadRootId = QString(),
|
||||||
|
const QString &fallbackId = QString());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the room avatar.
|
* @brief Set the room avatar.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -161,5 +161,3 @@ QUrl NeochatRoomMember::avatarUrl() const
|
|||||||
|
|
||||||
return m_room->member(m_memberId).avatarUrl();
|
return m_room->member(m_memberId).avatarUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_neochatroommember.cpp"
|
|
||||||
|
|||||||
@@ -248,9 +248,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
|||||||
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
|
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
|
||||||
TextHandler textHandler;
|
TextHandler textHandler;
|
||||||
textHandler.setData(text);
|
textHandler.setData(text);
|
||||||
auto content = std::make_unique<Quotient::EventContent::TextContent>(textHandler.handleSendText(), u"text/html"_s);
|
room->postMessage(text, textHandler.handleSendText(), RoomMessageEvent::MsgType::Text, replyEventId, QString());
|
||||||
EventRelation relatesTo = EventRelation::replyTo(replyEventId);
|
|
||||||
room->post<Quotient::RoomMessageEvent>(text, MessageEventType::Text, std::move(content), relatesTo);
|
|
||||||
});
|
});
|
||||||
notification->setReplyAction(std::move(replyAction));
|
notification->setReplyAction(std::move(replyAction));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,8 +168,9 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId
|
|||||||
ownAnswers.insert(0, answerId);
|
ownAnswers.insert(0, answerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &response = room->post<PollResponseEvent>(eventId, ownAnswers);
|
auto response = new PollResponseEvent(eventId, ownAnswers);
|
||||||
handleAnswer(response->contentJson(), room->localMember().id(), QDateTime::currentDateTime());
|
handleAnswer(response->contentJson(), room->localMember().id(), QDateTime::currentDateTime());
|
||||||
|
room->postEvent(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PollHandler::hasEnded() const
|
bool PollHandler::hasEnded() const
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ QQC2.Menu {
|
|||||||
margins: Kirigami.Units.smallSpacing
|
margins: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18nc("@action:button", "Show QR Code")
|
text: i18nc("@action:button", "Show QR code")
|
||||||
icon.name: "view-barcode-qr-symbolic"
|
icon.name: "view-barcode-qr-symbolic"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
let qrMax = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
let qrMax = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||||
@@ -37,7 +37,7 @@ QQC2.Menu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18n("Edit This Account")
|
text: i18n("Edit this account")
|
||||||
icon.name: "document-edit"
|
icon.name: "document-edit"
|
||||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), {
|
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), {
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
@@ -46,7 +46,7 @@ QQC2.Menu {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18n("Notification Settings")
|
text: i18n("Notification settings")
|
||||||
icon.name: "notifications"
|
icon.name: "notifications"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
NeoChatSettingsView.open('notifications');
|
NeoChatSettingsView.open('notifications');
|
||||||
@@ -60,7 +60,7 @@ QQC2.Menu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18n("Open Developer Tools")
|
text: i18n("Open developer tools")
|
||||||
icon.name: "tools"
|
icon.name: "tools"
|
||||||
visible: NeoChatConfig.developerTools
|
visible: NeoChatConfig.developerTools
|
||||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
|
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
|
||||||
@@ -80,7 +80,7 @@ QQC2.Menu {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18nc("@action:inmenu", "Verify This Device")
|
text: i18nc("@action:inmenu", "Verify this Device")
|
||||||
icon.name: "security-low"
|
icon.name: "security-low"
|
||||||
onTriggered: root.connection.startSelfVerification()
|
onTriggered: root.connection.startSelfVerification()
|
||||||
enabled: Controller.csSupported
|
enabled: Controller.csSupported
|
||||||
|
|||||||
@@ -23,11 +23,7 @@ SearchPage {
|
|||||||
|
|
||||||
model: RoomManager.sortFilterRoomListModel
|
model: RoomManager.sortFilterRoomListModel
|
||||||
modelDelegate: RoomDelegate {
|
modelDelegate: RoomDelegate {
|
||||||
onClicked: {
|
onClicked: root.chosen(currentRoom.id)
|
||||||
root.chosen(currentRoom.id);
|
|
||||||
root.closeDialog();
|
|
||||||
}
|
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
openOnClick: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,17 +27,38 @@ Loader {
|
|||||||
Component {
|
Component {
|
||||||
id: regularMenu
|
id: regularMenu
|
||||||
QQC2.Menu {
|
QQC2.Menu {
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: room.isFavourite ? i18n("Remove from Favorites") : i18n("Add to Favorites")
|
||||||
|
icon.name: room.isFavourite ? "bookmark-remove" : "bookmark-new"
|
||||||
|
onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: room.isLowPriority ? i18n("Reprioritize") : i18n("Deprioritize")
|
||||||
|
icon.name: room.isLowPriority ? "arrow-up-symbolic" : "arrow-down-symbolic"
|
||||||
|
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18n("Mark as Read")
|
text: i18n("Mark as Read")
|
||||||
icon.name: "checkmark"
|
icon.name: "checkmark"
|
||||||
enabled: room.notificationCount > 0
|
|
||||||
onTriggered: room.markAllMessagesAsRead()
|
onTriggered: room.markAllMessagesAsRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.MenuSeparator {}
|
QQC2.MenuItem {
|
||||||
|
text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID to Clipboard") : i18nc("@action:inmenu", "Copy Address to Clipboard")
|
||||||
|
icon.name: "edit-copy"
|
||||||
|
onTriggered: if (room.isDirectChat()) {
|
||||||
|
Clipboard.saveText(room.directChatRemoteMember.id);
|
||||||
|
} else if (room.canonicalAlias.length === 0) {
|
||||||
|
Clipboard.saveText(room.id);
|
||||||
|
} else {
|
||||||
|
Clipboard.saveText(room.canonicalAlias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.Menu {
|
QQC2.Menu {
|
||||||
title: i18nc("@action:inmenu", "Notifications")
|
title: i18n("Notification State")
|
||||||
icon.name: "notifications"
|
icon.name: "notifications"
|
||||||
|
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
@@ -86,32 +107,6 @@ Loader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.MenuItem {
|
|
||||||
text: room.isFavourite ? i18n("Remove from Favorites") : i18n("Add to Favorites")
|
|
||||||
icon.name: room.isFavourite ? "rating" : "rating-unrated"
|
|
||||||
onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.MenuItem {
|
|
||||||
text: room.isLowPriority ? i18n("Reprioritize") : i18n("Deprioritize")
|
|
||||||
icon.name: room.isLowPriority ? "arrow-up-symbolic" : "arrow-down-symbolic"
|
|
||||||
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.MenuSeparator {}
|
|
||||||
|
|
||||||
QQC2.MenuItem {
|
|
||||||
text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID to Clipboard") : i18nc("@action:inmenu", "Copy Address to Clipboard")
|
|
||||||
icon.name: "edit-copy"
|
|
||||||
onTriggered: if (room.isDirectChat()) {
|
|
||||||
Clipboard.saveText(room.directChatRemoteMember.id);
|
|
||||||
} else if (room.canonicalAlias.length === 0) {
|
|
||||||
Clipboard.saveText(room.id);
|
|
||||||
} else {
|
|
||||||
Clipboard.saveText(room.canonicalAlias);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18nc("@action:inmenu", "Room Settings")
|
text: i18nc("@action:inmenu", "Room Settings")
|
||||||
icon.name: 'settings-configure-symbolic'
|
icon.name: 'settings-configure-symbolic'
|
||||||
|
|||||||
@@ -57,11 +57,6 @@ Loader {
|
|||||||
*/
|
*/
|
||||||
property string selectedText: ""
|
property string selectedText: ""
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The link the user has currently hovered.
|
|
||||||
*/
|
|
||||||
property string hoveredLink: ""
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The list of menu item actions that have sub-actions.
|
* @brief The list of menu item actions that have sub-actions.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ SearchPage {
|
|||||||
signal roomSelected(string roomId, string displayName, url avatarUrl, string alias, string topic, int memberCount, bool isJoined)
|
signal roomSelected(string roomId, string displayName, url avatarUrl, string alias, string topic, int memberCount, bool isJoined)
|
||||||
|
|
||||||
title: i18nc("@action:title", "Explore Rooms")
|
title: i18nc("@action:title", "Explore Rooms")
|
||||||
customPlaceholderText: publicRoomListModel.redirectedText
|
|
||||||
customPlaceholderIcon: "data-warning"
|
|
||||||
|
|
||||||
Component.onCompleted: focusSearch()
|
Component.onCompleted: focusSearch()
|
||||||
|
|
||||||
@@ -95,7 +93,6 @@ SearchPage {
|
|||||||
|
|
||||||
activeFocusOnTab: false // We handle moving to this item via up/down arrows, otherwise the tab order is wacky
|
activeFocusOnTab: false // We handle moving to this item via up/down arrows, otherwise the tab order is wacky
|
||||||
text: i18n("Enter a Room Manually")
|
text: i18n("Enter a Room Manually")
|
||||||
visible: publicRoomListModel.redirectedText.length === 0
|
|
||||||
icon.name: "compass"
|
icon.name: "compass"
|
||||||
icon.width: Kirigami.Units.gridUnit * 2
|
icon.width: Kirigami.Units.gridUnit * 2
|
||||||
icon.height: Kirigami.Units.gridUnit * 2
|
icon.height: Kirigami.Units.gridUnit * 2
|
||||||
|
|||||||
@@ -42,19 +42,15 @@ DelegateContextMenu {
|
|||||||
* Each action will be instantiated as a single line in the menu.
|
* Each action will be instantiated as a single line in the menu.
|
||||||
*/
|
*/
|
||||||
property list<Kirigami.Action> actions: [
|
property list<Kirigami.Action> actions: [
|
||||||
DelegateContextMenu.ReplyMessageAction {},
|
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
separator: true
|
text: i18n("Open Externally")
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18nc("@action:inmenu", "Open Image")
|
|
||||||
icon.name: "document-open"
|
icon.name: "document-open"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
currentRoom.openEventMediaExternally(root.eventId);
|
currentRoom.openEventMediaExternally(root.eventId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18nc("@action:inmenu", "Save Image…")
|
text: i18n("Save As")
|
||||||
icon.name: "document-save"
|
icon.name: "document-save"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
|
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
|
||||||
@@ -62,16 +58,14 @@ DelegateContextMenu {
|
|||||||
dialog.open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
DelegateContextMenu.ReplyMessageAction {},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18nc("@action:inmenu", "Copy Image")
|
text: i18n("Copy")
|
||||||
icon.name: "edit-copy"
|
icon.name: "edit-copy"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
currentRoom.copyEventMedia(root.eventId);
|
currentRoom.copyEventMedia(root.eventId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Kirigami.Action {
|
|
||||||
separator: true
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
visible: author.id === currentRoom.localMember.id || currentRoom.canSendState("redact")
|
visible: author.id === currentRoom.localMember.id || currentRoom.canSendState("redact")
|
||||||
text: i18n("Remove")
|
text: i18n("Remove")
|
||||||
@@ -94,13 +88,7 @@ DelegateContextMenu {
|
|||||||
},
|
},
|
||||||
DelegateContextMenu.ReportMessageAction {},
|
DelegateContextMenu.ReportMessageAction {},
|
||||||
DelegateContextMenu.ShowUserAction {},
|
DelegateContextMenu.ShowUserAction {},
|
||||||
Kirigami.Action {
|
DelegateContextMenu.ViewSourceAction {}
|
||||||
separator: true
|
|
||||||
visible: viewSourceAction.visible
|
|
||||||
},
|
|
||||||
DelegateContextMenu.ViewSourceAction {
|
|
||||||
id: viewSourceAction
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,15 +20,8 @@ Kirigami.ApplicationWindow {
|
|||||||
|
|
||||||
property bool initialized: false
|
property bool initialized: false
|
||||||
|
|
||||||
title: {
|
|
||||||
if (NeoChatConfig.windowTitleFocus) {
|
title: NeoChatConfig.windowTitleFocus ? activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "") : "NeoChat"
|
||||||
return activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "");
|
|
||||||
} else if (RoomManager.currentRoom) {
|
|
||||||
return RoomManager.currentRoom.displayName;
|
|
||||||
} else {
|
|
||||||
return Application.displayName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
minimumWidth: Kirigami.Units.gridUnit * 20
|
minimumWidth: Kirigami.Units.gridUnit * 20
|
||||||
minimumHeight: Kirigami.Units.gridUnit * 15
|
minimumHeight: Kirigami.Units.gridUnit * 15
|
||||||
@@ -92,7 +85,7 @@ Kirigami.ApplicationWindow {
|
|||||||
target: RoomManager
|
target: RoomManager
|
||||||
|
|
||||||
function onCurrentRoomChanged() {
|
function onCurrentRoomChanged() {
|
||||||
if (RoomManager.currentRoom && pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
|
if (RoomManager.currentRoom && pageStack.depth <= 1 && initialized && Kirigami.Settings.isMobile) {
|
||||||
let roomPage = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
|
let roomPage = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
});
|
});
|
||||||
@@ -307,7 +300,7 @@ Kirigami.ApplicationWindow {
|
|||||||
target: ShareHandler
|
target: ShareHandler
|
||||||
function onTextChanged(): void {
|
function onTextChanged(): void {
|
||||||
if (root.connection && ShareHandler.text.length > 0) {
|
if (root.connection && ShareHandler.text.length > 0) {
|
||||||
root.handleShare();
|
handleShare();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ DelegateContextMenu {
|
|||||||
},
|
},
|
||||||
DelegateContextMenu.ReplyMessageAction {},
|
DelegateContextMenu.ReplyMessageAction {},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18nc("@action:inmenu As in 'Forward this message'", "Forward…")
|
text: i18nc("@action:inmenu As in 'Forward this message'", "Forward")
|
||||||
icon.name: "mail-forward-symbolic"
|
icon.name: "mail-forward-symbolic"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
let page = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
let page = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
||||||
@@ -59,39 +59,21 @@ DelegateContextMenu {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Kirigami.Action {
|
|
||||||
separator: true
|
|
||||||
},
|
|
||||||
DelegateContextMenu.RemoveMessageAction {},
|
DelegateContextMenu.RemoveMessageAction {},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18nc("@action:inmenu", "Copy Link Address")
|
text: i18n("Copy")
|
||||||
icon.name: "edit-copy"
|
|
||||||
visible: root.hoveredLink.length > 0
|
|
||||||
onTriggered: Clipboard.saveText(root.hoveredLink)
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18nc("@action:inmenu", "Copy Text")
|
|
||||||
icon.name: "edit-copy"
|
icon.name: "edit-copy"
|
||||||
onTriggered: Clipboard.saveText(root.selectedText.length > 0 ? root.selectedText : root.plainText)
|
onTriggered: Clipboard.saveText(root.selectedText.length > 0 ? root.selectedText : root.plainText)
|
||||||
},
|
},
|
||||||
|
DelegateContextMenu.ReportMessageAction {},
|
||||||
|
DelegateContextMenu.ShowUserAction {},
|
||||||
|
DelegateContextMenu.ViewSourceAction {},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18nc("@action:inmenu", "Copy Message Link")
|
text: i18n("Copy Link")
|
||||||
icon.name: "edit-copy"
|
icon.name: "edit-copy"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Clipboard.saveText("https://matrix.to/#/" + currentRoom.id + "/" + root.eventId);
|
Clipboard.saveText("https://matrix.to/#/" + currentRoom.id + "/" + root.eventId);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
separator: true
|
|
||||||
},
|
|
||||||
DelegateContextMenu.ReportMessageAction {},
|
|
||||||
DelegateContextMenu.ShowUserAction {},
|
|
||||||
Kirigami.Action {
|
|
||||||
separator: true
|
|
||||||
visible: viewSourceAction.visible
|
|
||||||
},
|
|
||||||
DelegateContextMenu.ViewSourceAction {
|
|
||||||
id: viewSourceAction
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ Delegates.RoundedItemDelegate {
|
|||||||
required property string subtitleText
|
required property string subtitleText
|
||||||
required property string displayName
|
required property string displayName
|
||||||
|
|
||||||
property bool openOnClick: true
|
|
||||||
property bool showConfigure: true
|
property bool showConfigure: true
|
||||||
|
|
||||||
property bool collapsed: false
|
property bool collapsed: false
|
||||||
@@ -36,10 +35,8 @@ Delegates.RoundedItemDelegate {
|
|||||||
Accessible.onPressAction: clicked()
|
Accessible.onPressAction: clicked()
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (root.openOnClick) {
|
RoomManager.resolveResource(currentRoom.id);
|
||||||
RoomManager.resolveResource(currentRoom.id);
|
pageStack.currentIndex = 1;
|
||||||
pageStack.currentIndex = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressAndHold: createRoomListContextMenu()
|
onPressAndHold: createRoomListContextMenu()
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ Kirigami.OverlayDrawer {
|
|||||||
required property NeoChatConnection connection
|
required property NeoChatConnection connection
|
||||||
|
|
||||||
width: actualWidth
|
width: actualWidth
|
||||||
interactive: modal
|
|
||||||
|
|
||||||
readonly property int minWidth: Kirigami.Units.gridUnit * 15
|
readonly property int minWidth: Kirigami.Units.gridUnit * 15
|
||||||
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
|
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ QQC2.ScrollView {
|
|||||||
/**
|
/**
|
||||||
* @brief The title that should be displayed for this component if available.
|
* @brief The title that should be displayed for this component if available.
|
||||||
*/
|
*/
|
||||||
readonly property string title: root.room.isSpace ? i18nc("@action:title", "Space Members") : i18nc("@action:title", "Room Information")
|
readonly property string title: root.room.isSpace ? i18nc("@action:title", "Space Members") : i18nc("@action:title", "Room information")
|
||||||
|
|
||||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||||
@@ -133,7 +133,7 @@ QQC2.ScrollView {
|
|||||||
Delegates.RoundedItemDelegate {
|
Delegates.RoundedItemDelegate {
|
||||||
id: leaveButton
|
id: leaveButton
|
||||||
icon.name: "arrow-left-symbolic"
|
icon.name: "arrow-left-symbolic"
|
||||||
text: root.room.isSpace ? i18nc("@action:button", "Leave this space") : i18nc("@action:button", "Leave this room")
|
text: i18nc("@action:button", "Leave this room")
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ Kirigami.Page {
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: _private
|
id: _private
|
||||||
property int currentWidth: NeoChatConfig.collapsed ? collapsedSize : defaultWidth
|
property int currentWidth: NeoChatConfig.collapsed ? collapsedSize : defaultWidth
|
||||||
readonly property int defaultWidth: Kirigami.Units.gridUnit * 15
|
readonly property int defaultWidth: Kirigami.Units.gridUnit * 17
|
||||||
readonly property int collapseWidth: Kirigami.Units.gridUnit * 10
|
readonly property int collapseWidth: Kirigami.Units.gridUnit * 10
|
||||||
readonly property int collapsedSize: Kirigami.Units.gridUnit + (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2) + Kirigami.Units.largeSpacing * 2 + (scrollView.QQC2.ScrollBar.vertical.visible ? scrollView.QQC2.ScrollBar.vertical.width : 0)
|
readonly property int collapsedSize: Kirigami.Units.gridUnit + (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2) + Kirigami.Units.largeSpacing * 2 + (scrollView.QQC2.ScrollBar.vertical.visible ? scrollView.QQC2.ScrollBar.vertical.width : 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,23 +163,6 @@ Kirigami.Page {
|
|||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
color: NeoChatConfig.compactLayout ? Kirigami.Theme.backgroundColor : "transparent"
|
color: NeoChatConfig.compactLayout ? Kirigami.Theme.backgroundColor : "transparent"
|
||||||
|
|
||||||
Image {
|
|
||||||
visible: !NeoChatConfig.compactLayout && !NeoChatConfig.blur
|
|
||||||
source: "qrc:/qt/qml/org/kde/neochat/timeline/images/chat-page-background.svg"
|
|
||||||
anchors.fill: parent
|
|
||||||
fillMode: Image.Tile
|
|
||||||
horizontalAlignment: Image.AlignLeft
|
|
||||||
verticalAlignment: Image.AlignTop
|
|
||||||
|
|
||||||
function isDarkColor(background: color): bool {
|
|
||||||
const temp = Qt.darker(background, 1);
|
|
||||||
const darkness = 1 - (0.299 * temp.r + 0.587 * temp.g + 0.114 * temp.b);
|
|
||||||
return temp.a > 0 && darkness >= 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
opacity: isDarkColor(Kirigami.Theme.backgroundColor) ? 0.2 : 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: Loader {
|
footer: Loader {
|
||||||
@@ -262,10 +245,9 @@ Kirigami.Page {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText, hoveredLink, isThread) {
|
function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText, isThread) {
|
||||||
const contextMenu = messageDelegateContextMenu.createObject(root, {
|
const contextMenu = messageDelegateContextMenu.createObject(root, {
|
||||||
selectedText: selectedText,
|
selectedText: selectedText,
|
||||||
hoveredLink: hoveredLink,
|
|
||||||
author: author,
|
author: author,
|
||||||
eventId: eventId,
|
eventId: eventId,
|
||||||
messageComponentType: messageComponentType,
|
messageComponentType: messageComponentType,
|
||||||
|
|||||||
@@ -80,17 +80,6 @@ Kirigami.ScrollablePage {
|
|||||||
*/
|
*/
|
||||||
property bool showSearchButton: true
|
property bool showSearchButton: true
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Message to be shown in a custom placeholder.
|
|
||||||
* The custom placeholder will be shown if the text is not empty
|
|
||||||
*/
|
|
||||||
property alias customPlaceholderText: customPlaceholder.text
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief icon for the custom placeholder
|
|
||||||
*/
|
|
||||||
property string customPlaceholderIcon: ""
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Force the search field to be focussed.
|
* @brief Force the search field to be focussed.
|
||||||
*/
|
*/
|
||||||
@@ -178,25 +167,18 @@ Kirigami.ScrollablePage {
|
|||||||
Kirigami.PlaceholderMessage {
|
Kirigami.PlaceholderMessage {
|
||||||
id: noSearchMessage
|
id: noSearchMessage
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: searchField.text.length === 0 && listView.count === 0 && customPlaceholder.text.length === 0
|
visible: searchField.text.length === 0 && listView.count === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
Kirigami.PlaceholderMessage {
|
||||||
id: noResultMessage
|
id: noResultMessage
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && customPlaceholder.text.length === 0
|
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
|
||||||
id: customPlaceholder
|
|
||||||
anchors.centerIn: parent
|
|
||||||
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && text.length > 0
|
|
||||||
icon.name: root.customPlaceholderIcon
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.LoadingPlaceholder {
|
Kirigami.LoadingPlaceholder {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: searchField.text.length > 0 && listView.count === 0 && root.model.searching && customPlaceholder.text.length === 0
|
visible: searchField.text.length > 0 && listView.count === 0 && root.model.searching
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onUpPressed: {
|
Keys.onUpPressed: {
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ QQC2.Control {
|
|||||||
|
|
||||||
text: i18nc("@button View all one-on-one chats with your friends.", "Friends")
|
text: i18nc("@button View all one-on-one chats with your friends.", "Friends")
|
||||||
contentItem: Kirigami.Icon {
|
contentItem: Kirigami.Icon {
|
||||||
source: "system-users-symbolic"
|
source: "system-users"
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: directChatNotificationCountLabel
|
id: directChatNotificationCountLabel
|
||||||
@@ -200,6 +200,7 @@ QQC2.Control {
|
|||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
|
|
||||||
onSelected: {
|
onSelected: {
|
||||||
|
RoomManager.resolveResource(spaceDelegate.roomId);
|
||||||
RoomManager.currentSpace = spaceDelegate.roomId;
|
RoomManager.currentSpace = spaceDelegate.roomId;
|
||||||
}
|
}
|
||||||
checked: RoomManager.currentSpace === roomId
|
checked: RoomManager.currentSpace === roomId
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ ColumnLayout {
|
|||||||
onClicked: _private.createRoom(root.currentRoom.id)
|
onClicked: _private.createRoom(root.currentRoom.id)
|
||||||
}
|
}
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
text: i18nc("@action:button", "Leave this space")
|
text: i18nc("@button", "Leave the space")
|
||||||
icon.name: "go-previous"
|
icon.name: "go-previous"
|
||||||
onClicked: RoomManager.leaveRoom(root.currentRoom)
|
onClicked: RoomManager.leaveRoom(root.currentRoom)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ void RoomManager::viewEventSource(const QString &eventId)
|
|||||||
Q_EMIT showEventSource(eventId);
|
Q_EMIT showEventSource(eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText, const QString &hoveredLink)
|
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText)
|
||||||
{
|
{
|
||||||
const auto &event = **room->findInTimeline(eventId);
|
const auto &event = **room->findInTimeline(eventId);
|
||||||
|
|
||||||
@@ -214,8 +214,7 @@ void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, Neoch
|
|||||||
MessageComponentType::typeForEvent(event),
|
MessageComponentType::typeForEvent(event),
|
||||||
EventHandler::plainBody(room, &event),
|
EventHandler::plainBody(room, &event),
|
||||||
EventHandler::richBody(room, &event),
|
EventHandler::richBody(room, &event),
|
||||||
selectedText,
|
selectedText);
|
||||||
hoveredLink);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RoomManager::hasOpenRoom() const
|
bool RoomManager::hasOpenRoom() const
|
||||||
@@ -456,12 +455,10 @@ void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_isMobile) {
|
if (spaceId.length() > 3) {
|
||||||
if (spaceId.length() > 3) {
|
resolveResource(spaceId, "no_join"_ls);
|
||||||
resolveResource(spaceId, "no_join"_ls);
|
} else if (!m_isMobile) {
|
||||||
} else {
|
visitRoom({}, {});
|
||||||
visitRoom({}, {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user