Compare commits
134 Commits
work/nvrwh
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80ebe71f6a | ||
|
|
02578dd38f | ||
|
|
b83d42103f | ||
|
|
c9856347fe | ||
|
|
7224c92caf | ||
|
|
a84f98c96f | ||
|
|
a40bccc29d | ||
|
|
171897161c | ||
|
|
013ad49e2b | ||
|
|
b357586164 | ||
|
|
5642f3416a | ||
|
|
9bbb1710df | ||
|
|
e2b7679252 | ||
|
|
5c353cd4b5 | ||
|
|
cba6dc994f | ||
|
|
b0c4b7fc2a | ||
|
|
ed7aff1f24 | ||
|
|
33f4be0d88 | ||
|
|
1178cafef0 | ||
|
|
1be97e65b4 | ||
|
|
634cefc694 | ||
|
|
36469c6097 | ||
|
|
0cd9c6a434 | ||
|
|
e285a94273 | ||
|
|
a2ec6d97b1 | ||
|
|
b42fd3fc51 | ||
|
|
12971bb08b | ||
|
|
79deda4f2d | ||
|
|
365af2cd6c | ||
|
|
cc50e76c0d | ||
|
|
16df22af68 | ||
|
|
53a957fa15 | ||
|
|
69571489fa | ||
|
|
3a66b4d67e | ||
|
|
ba8d2b1281 | ||
|
|
11185c127d | ||
|
|
0ab3bfd4f3 | ||
|
|
b5fdb3d0db | ||
|
|
9ef342e448 | ||
|
|
0bda65b5ac | ||
|
|
005b7a760c | ||
|
|
ab3c40a709 | ||
|
|
a809b7f11e | ||
|
|
702c7d49a8 | ||
|
|
a2afaf40cd | ||
|
|
eb900a5c2c | ||
|
|
14eadb3b92 | ||
|
|
a5d84bb266 | ||
|
|
5a03ce4e95 | ||
|
|
607db82db0 | ||
|
|
56b302d4c8 | ||
|
|
1237e9d4bd | ||
|
|
71468e453c | ||
|
|
d0a915e81c | ||
|
|
e7751f40fa | ||
|
|
498240c6ec | ||
|
|
d448cf4c7e | ||
|
|
d7d9f254ff | ||
|
|
aedba5c650 | ||
|
|
a4e9794b13 | ||
|
|
4bd4bd6f22 | ||
|
|
ac9bfbff78 | ||
|
|
e26392ce94 | ||
|
|
a314e56425 | ||
|
|
4af27f7609 | ||
|
|
773017c881 | ||
|
|
153cbeae8a | ||
|
|
777ea9fbe0 | ||
|
|
d22cc7f40a | ||
|
|
c28ca9087c | ||
|
|
857412d9cc | ||
|
|
4b132460f6 | ||
|
|
644f5c0ce1 | ||
|
|
45439e17c9 | ||
|
|
485e0f0510 | ||
|
|
2969d20a92 | ||
|
|
7cf97a5c9b | ||
|
|
f2894ed774 | ||
|
|
3e1044b8fd | ||
|
|
0c6480c5e0 | ||
|
|
232c45979a | ||
|
|
848031c315 | ||
|
|
bf2389c31c | ||
|
|
d87bba7993 | ||
|
|
9b837a8656 | ||
|
|
90061caec3 | ||
|
|
6e81701d4b | ||
|
|
2164c4c3f8 | ||
|
|
a0665187c6 | ||
|
|
77e933f620 | ||
|
|
deed9cf6d7 | ||
|
|
fad4e506bc | ||
|
|
9d6b940b78 | ||
|
|
cfb663d399 | ||
|
|
4620c176b6 | ||
|
|
8c927a59d9 | ||
|
|
2fe9bc9846 | ||
|
|
b9615eadb3 | ||
|
|
e4e5d14d6e | ||
|
|
75eddd2a6f | ||
|
|
634407a22d | ||
|
|
42dd2e5413 | ||
|
|
2a6e3c0add | ||
|
|
abb81e0d8e | ||
|
|
e9a4b43331 | ||
|
|
08b2c39a61 | ||
|
|
f89cec9c55 | ||
|
|
4c49ca2a51 | ||
|
|
52ab6f484b | ||
|
|
2a5359e73b | ||
|
|
977ea73237 | ||
|
|
a769b904dc | ||
|
|
5def5124ef | ||
|
|
40c3cc7f9e | ||
|
|
17c0906044 | ||
|
|
38cfc915f1 | ||
|
|
1821d9fc04 | ||
|
|
ec6a8dd028 | ||
|
|
e0c3b7f808 | ||
|
|
155dc4919e | ||
|
|
a38c53b2be | ||
|
|
67dfc7b32e | ||
|
|
f156551d4f | ||
|
|
5c04eb85af | ||
|
|
1efa27177a | ||
|
|
17da652152 | ||
|
|
4ff866ea29 | ||
|
|
3c9c7abe35 | ||
|
|
157017126a | ||
|
|
928e4ae5ed | ||
|
|
2bc790f4cb | ||
|
|
a3148b264c | ||
|
|
16b27700f5 | ||
|
|
9ee10b6968 |
@@ -3,5 +3,4 @@
|
||||
|
||||
[BlueprintSettings]
|
||||
kde/applications/neochat.packageAppx=True
|
||||
kde/frameworks/extra-cmake-modules.version=master
|
||||
libs/qt.qtMajorVersion=6
|
||||
|
||||
@@ -10,7 +10,8 @@ include:
|
||||
- /gitlab-templates/windows-qt6.yml
|
||||
# - /gitlab-templates/freebsd-qt6.yml
|
||||
- /gitlab-templates/flatpak.yml
|
||||
- /gitlab-templates/snap-snapcraft-lxd.yml
|
||||
- /gitlab-templates/craft-android-qt6-apks.yml
|
||||
- /gitlab-templates/craft-appimage-qt6.yml
|
||||
- /gitlab-templates/craft-windows-x86-64-qt6.yml
|
||||
- /gitlab-templates/craft-windows-appx-qt6.yml
|
||||
- /gitlab-templates/craft-windows-appx-qt6.yml
|
||||
|
||||
@@ -14,7 +14,7 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "6.5")
|
||||
set(KF_MIN_VERSION "6.6")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
@@ -39,6 +39,8 @@ include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
include(ECMAddAndroidApk)
|
||||
include(ECMQmlModule)
|
||||
include(GenerateExportHeader)
|
||||
include(ECMGenerateHeaders)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
@@ -101,7 +103,7 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
||||
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -55,5 +55,6 @@
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- for Android >= 33 -->
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -82,3 +82,9 @@ ecm_add_test(
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME linkpreviewertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
messagecontentmodeltest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME messagecontentmodeltest
|
||||
)
|
||||
|
||||
@@ -14,7 +14,6 @@ class ActionsHandlerTest : public QObject
|
||||
|
||||
private:
|
||||
Quotient::Connection *connection = Quotient::Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
ActionsHandler *actionsHandler = new ActionsHandler(this);
|
||||
|
||||
private Q_SLOTS:
|
||||
void nullObject();
|
||||
@@ -23,20 +22,19 @@ private Q_SLOTS:
|
||||
void ActionsHandlerTest::nullObject()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
actionsHandler->handleMessageEvent(nullptr);
|
||||
ActionsHandler::handleMessageEvent(nullptr, nullptr);
|
||||
|
||||
auto chatBarCache = new ChatBarCache(this);
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
actionsHandler->handleMessageEvent(chatBarCache);
|
||||
ActionsHandler::handleMessageEvent(nullptr, chatBarCache);
|
||||
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
|
||||
actionsHandler->setRoom(room);
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
actionsHandler->handleMessageEvent(nullptr);
|
||||
ActionsHandler::handleMessageEvent(room, nullptr);
|
||||
|
||||
// The final one should throw no warning so we make sure.
|
||||
QTest::failOnWarning("ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
actionsHandler->handleMessageEvent(chatBarCache);
|
||||
ActionsHandler::handleMessageEvent(room, chatBarCache);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(ActionsHandlerTest)
|
||||
|
||||
@@ -51,7 +51,7 @@ void ChatBarCacheTest::empty()
|
||||
QCOMPARE(chatBarCache->replyId(), QString());
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
QCOMPARE(chatBarCache->editId(), QString());
|
||||
QCOMPARE(chatBarCache->relationUser(), room->member(QString()));
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(QString()));
|
||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
}
|
||||
@@ -65,7 +65,7 @@ void ChatBarCacheTest::noRoom()
|
||||
// ChatBarCache has no parent.
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember());
|
||||
QCOMPARE(chatBarCache->relationAuthor(), Quotient::RoomMember());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||
@@ -81,7 +81,7 @@ void ChatBarCacheTest::badParent()
|
||||
// ChatBarCache has no parent.
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember());
|
||||
QCOMPARE(chatBarCache->relationAuthor(), Quotient::RoomMember());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||
@@ -99,7 +99,7 @@ void ChatBarCacheTest::reply()
|
||||
QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org"));
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
QCOMPARE(chatBarCache->editId(), QString());
|
||||
QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org")));
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(QLatin1String("@example:example.org")));
|
||||
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
}
|
||||
@@ -121,7 +121,7 @@ void ChatBarCacheTest::edit()
|
||||
QCOMPARE(chatBarCache->replyId(), QString());
|
||||
QCOMPARE(chatBarCache->isEditing(), true);
|
||||
QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org"));
|
||||
QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org")));
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(QLatin1String("@example:example.org")));
|
||||
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
}
|
||||
@@ -138,7 +138,7 @@ void ChatBarCacheTest::attachment()
|
||||
QCOMPARE(chatBarCache->replyId(), QString());
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
QCOMPARE(chatBarCache->editId(), QString());
|
||||
QCOMPARE(chatBarCache->relationUser(), room->member(QString()));
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(QString()));
|
||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
|
||||
}
|
||||
|
||||
@@ -213,11 +213,13 @@ void EventHandlerTest::genericBody_data()
|
||||
QTest::addColumn<int>("eventNum");
|
||||
QTest::addColumn<QString>("output");
|
||||
|
||||
QTest::newRow("message") << 0 << QStringLiteral("sent a message");
|
||||
QTest::newRow("member") << 1 << QStringLiteral("changed their display name and updated their avatar");
|
||||
QTest::newRow("message 2") << 2 << QStringLiteral("sent a message");
|
||||
QTest::newRow("message") << 0 << QStringLiteral("<a href=\"https://matrix.to/#/@example:example.org\">after</a> sent a message");
|
||||
QTest::newRow("member") << 1
|
||||
<< QStringLiteral(
|
||||
"<a href=\"https://matrix.to/#/@example:example.org\">after</a> changed their display name and updated their avatar");
|
||||
QTest::newRow("message 2") << 2 << QStringLiteral("<a href=\"https://matrix.to/#/@example:example.org\">after</a> sent a message");
|
||||
QTest::newRow("reaction") << 3 << QStringLiteral("Unknown event");
|
||||
QTest::newRow("video") << 4 << QStringLiteral("sent a message");
|
||||
QTest::newRow("video") << 4 << QStringLiteral("<a href=\"https://matrix.to/#/@example:example.org\">after</a> sent a message");
|
||||
}
|
||||
|
||||
void EventHandlerTest::genericBody()
|
||||
@@ -225,13 +227,16 @@ void EventHandlerTest::genericBody()
|
||||
QFETCH(int, eventNum);
|
||||
QFETCH(QString, output);
|
||||
|
||||
QCOMPARE(EventHandler::genericBody(room->messageEvents().at(eventNum).get()), output);
|
||||
QCOMPARE(EventHandler::genericBody(room, room->messageEvents().at(eventNum).get()), output);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullGenericBody()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "genericBody called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::genericBody(nullptr, nullptr), QString());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "genericBody called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::genericBody(nullptr), QString());
|
||||
QCOMPARE(EventHandler::genericBody(room, nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::markdownBody()
|
||||
|
||||
61
autotests/messagecontentmodeltest.cpp
Normal file
61
autotests/messagecontentmodeltest.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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 <QObject>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/roommember.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "models/messagecontentmodel.h"
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
class MessageContentModelTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void missingEvent();
|
||||
};
|
||||
|
||||
void MessageContentModelTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
}
|
||||
|
||||
void MessageContentModelTest::missingEvent()
|
||||
{
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#firstRoom:kde.org"));
|
||||
auto model1 = MessageContentModel(room, "$153456789:example.org"_L1);
|
||||
|
||||
QCOMPARE(model1.rowCount(), 1);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), "Loading"_L1);
|
||||
|
||||
auto model2 = MessageContentModel(room, "$153456789:example.org"_L1, true);
|
||||
|
||||
QCOMPARE(model2.rowCount(), 1);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), "Loading reply"_L1);
|
||||
|
||||
room->syncNewEvents(QLatin1String("test-min-sync.json"));
|
||||
QCOMPARE(model1.rowCount(), 2);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Author);
|
||||
QCOMPARE(model1.data(model1.index(1), MessageContentModel::ComponentTypeRole), MessageComponentType::Text);
|
||||
QCOMPARE(model1.data(model1.index(1), MessageContentModel::DisplayRole), u"<b>This is an example<br>text message</b>"_s);
|
||||
}
|
||||
|
||||
QTEST_MAIN(MessageContentModelTest)
|
||||
#include "messagecontentmodeltest.moc"
|
||||
@@ -12,9 +12,7 @@
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
@@ -86,11 +84,11 @@ void TextHandlerTest::initTestCase()
|
||||
|
||||
void TextHandlerTest::allowedAttributes()
|
||||
{
|
||||
const QString testInputString1 = QStringLiteral("<p><span data-mx-spoiler><font color=#FFFFFF>Test</font><span></p>");
|
||||
const QString testOutputString1 = QStringLiteral("<p><span data-mx-spoiler><font color=#FFFFFF>Test</font><span></p>");
|
||||
const QString testInputString1 = QStringLiteral("<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>");
|
||||
const QString testOutputString1 = QStringLiteral("<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>");
|
||||
// Handle urls where the href has either single (') or double (") quotes.
|
||||
const QString testInputString2 = QStringLiteral("<p><a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a></p>");
|
||||
const QString testOutputString2 = QStringLiteral("<p><a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a></p>");
|
||||
const QString testInputString2 = QStringLiteral("<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>");
|
||||
const QString testOutputString2 = QStringLiteral("<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString1);
|
||||
@@ -118,7 +116,7 @@ void TextHandlerTest::stripDisallowedTags()
|
||||
void TextHandlerTest::stripDisallowedAttributes()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p style=\"font-size:50px;\" color=#FFFFFF>Test</p>");
|
||||
const QString testOutputString = QStringLiteral("<p>Test</p>");
|
||||
const QString testOutputString = QStringLiteral("Test");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -145,8 +143,8 @@ void TextHandlerTest::emptyCodeTags()
|
||||
|
||||
void TextHandlerTest::sendSimpleStringCase()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("This data should just be put in a paragraph.");
|
||||
const QString testOutputString = QStringLiteral("<p>This data should just be put in a paragraph.</p>");
|
||||
const QString testInputString = QStringLiteral("This data should just be left alone.");
|
||||
const QString testOutputString = QStringLiteral("This data should just be left alone.");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -159,8 +157,8 @@ void TextHandlerTest::sendSingleParaMarkup()
|
||||
const QString testInputString = QStringLiteral(
|
||||
"Text para with **bold**, *italic*, [link](https://kde.org), , `inline code`.");
|
||||
const QString testOutputString = QStringLiteral(
|
||||
"<p>Text para with <strong>bold</strong>, <em>italic</em>, <a href=\"https://kde.org\">link</a>, <img "
|
||||
"src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\">, <code>inline code</code>.</p>");
|
||||
"Text para with <strong>bold</strong>, <em>italic</em>, <a href=\"https://kde.org\">link</a>, <img "
|
||||
"src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\">, <code>inline code</code>.");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -186,7 +184,7 @@ void TextHandlerTest::sendMultipleSectionMarkup()
|
||||
void TextHandlerTest::sendBadLinks()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("[link](kde.org), ");
|
||||
const QString testOutputString = QStringLiteral("<p><a>link</a>, <img alt=\"image\"></p>");
|
||||
const QString testOutputString = QStringLiteral("<a>link</a>, <img alt=\"image\">");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -223,8 +221,8 @@ void TextHandlerTest::sendCodeClass()
|
||||
void TextHandlerTest::sendCustomEmoji()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(":test:");
|
||||
const QString testOutputString = QStringLiteral(
|
||||
"<p><img data-mx-emoticon=\"\" src=\"mxc://example.org/test\" alt=\":test:\" title=\":test:\" height=\"32\" vertical-align=\"middle\" /></p>");
|
||||
const QString testOutputString =
|
||||
QStringLiteral("<img data-mx-emoticon=\"\" src=\"mxc://example.org/test\" alt=\":test:\" title=\":test:\" height=\"32\" vertical-align=\"middle\" />");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -237,7 +235,7 @@ void TextHandlerTest::sendCustomEmojiCode_data()
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("inline") << QStringLiteral("`:test:`") << QStringLiteral("<p><code>:test:</code></p>");
|
||||
QTest::newRow("inline") << QStringLiteral("`:test:`") << QStringLiteral("<code>:test:</code>");
|
||||
QTest::newRow("block") << QStringLiteral("```\n:test:\n```") << QStringLiteral("<pre><code>:test:\n</code></pre>");
|
||||
}
|
||||
|
||||
@@ -375,7 +373,7 @@ void TextHandlerTest::receivePlainStripMarkup()
|
||||
void TextHandlerTest::receiveRichUserPill()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>");
|
||||
const QString testOutputString = QStringLiteral("<p><b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b></p>");
|
||||
const QString testOutputString = QStringLiteral("<b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -386,7 +384,7 @@ void TextHandlerTest::receiveRichUserPill()
|
||||
void TextHandlerTest::receiveRichStrikethrough()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p><del>Test</del></p>");
|
||||
const QString testOutputString = QStringLiteral("<p><s>Test</s></p>");
|
||||
const QString testOutputString = QStringLiteral("<s>Test</s>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
@@ -50,41 +50,32 @@
|
||||
<name xml:lang="x-test">xxNeoChatxx</name>
|
||||
<name xml:lang="zh-CN">NeoChat</name>
|
||||
<name xml:lang="zh-TW">NeoChat</name>
|
||||
<summary>Chat with your friends on matrix</summary>
|
||||
<summary xml:lang="ar">دردش مع أصدقائك على ماتركس</summary>
|
||||
<summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
|
||||
<summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary>
|
||||
<summary xml:lang="cs">Mluvte se svými přáteli na Matrixu</summary>
|
||||
<summary xml:lang="en-GB">Chat with your friends on matrix</summary>
|
||||
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
|
||||
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
|
||||
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
|
||||
<summary xml:lang="fi">Keskustelu ystäviesi kanssa Matrixissa</summary>
|
||||
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
|
||||
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
|
||||
<summary xml:lang="he">התכתבות עם החברים שלך ב־matrix</summary>
|
||||
<summary xml:lang="hu">Csevegjen barátaival a matrixon</summary>
|
||||
<summary xml:lang="ia">Starta Conversation con tu amicos sur matrix</summary>
|
||||
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
|
||||
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
|
||||
<summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
|
||||
<summary xml:lang="lv">Tērzējiet ar saviem draugiem „Matrix“ tīklā</summary>
|
||||
<summary xml:lang="nl">Met uw vrienden chatten op matrix</summary>
|
||||
<summary xml:lang="nn">Prat med vennar på Matrix</summary>
|
||||
<summary xml:lang="pl">Rozmawiaj ze swoimi znajomymi w Matriksie</summary>
|
||||
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
|
||||
<summary xml:lang="sv">Chatta med dina vänner på Matrix</summary>
|
||||
<summary xml:lang="ta">மேட்ரிக்ஸு மூலம் உங்கள் நண்பர்களிடம் பேசலாம்</summary>
|
||||
<summary xml:lang="tr">Matrix’te arkadaşlarınızla sohbet edin</summary>
|
||||
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
|
||||
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
|
||||
<summary xml:lang="zh-CN">在 Matrix 上与朋友聊天</summary>
|
||||
<summary xml:lang="zh-TW">在 Matrix 上與您的朋友聊天</summary>
|
||||
<summary>Chat on Matrix</summary>
|
||||
<summary xml:lang="ar">دردش على ماتركس</summary>
|
||||
<summary xml:lang="ca">Xat a Matrix</summary>
|
||||
<summary xml:lang="ca-valencia">Xat a Matrix</summary>
|
||||
<summary xml:lang="es">Charle en Matrix</summary>
|
||||
<summary xml:lang="eu">Berriketa Matrix-en</summary>
|
||||
<summary xml:lang="fr">Discuter sur Matrix</summary>
|
||||
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
|
||||
<summary xml:lang="it">Chat su Matrix</summary>
|
||||
<summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary>
|
||||
<summary xml:lang="nl">Chat op Matrix</summary>
|
||||
<summary xml:lang="nn">Prat med via Matrix</summary>
|
||||
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
|
||||
<summary xml:lang="sl">Klepet na Matrixu</summary>
|
||||
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
|
||||
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
|
||||
<summary xml:lang="uk">Спілкування у Matrix</summary>
|
||||
<summary xml:lang="x-test">xxChat on Matrixxx</summary>
|
||||
<summary xml:lang="zh-TW">在 Matrix 上聊天</summary>
|
||||
<description>
|
||||
<p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
|
||||
<p xml:lang="ar">نيوتشات هو تطبيق دردشة يتيح لك الاستفادة الكاملة من شبكة Matrix. فهو يوفر لك طريقة آمنة لإرسال الرسائل النصية ومقاطع الفيديو والملفات الصوتية إلى عائلتك وزملائك وأصدقائك.</p>
|
||||
<p xml:lang="ca">El NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
|
||||
<p xml:lang="de">NeoChat ist eine Anwendung für Unterhaltungen mit allen Vorteilen des Matrix-Netzwerkes. Sie bietet eine sichere Möglichkeit zum Versenden von Nachrichten, Videos und Audiodateien and die Familienmitglieder.</p>
|
||||
<p xml:lang="el">Το NeoChat είναι μια εφαρμογή συνομιλίας που σας επιτρέπει να εκμεταλλευτείτε πλήρως το δίκτυο Matrix. Σας παρέχει έναν ασφαλή τρόπο να στέλνετε μηνύματα κειμένου, βίντεο και αρχεία ήχου στην οικογένεια, τους συναδέλφους και τους φίλους σας.</p>
|
||||
<p xml:lang="en-GB">NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
|
||||
<p xml:lang="eo">NeoChat estas babilej-apo, kiu ebligas al vi plene profiti de la Matrix-reto. Ĝi provizas al vi sekuran manieron sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj.</p>
|
||||
<p xml:lang="es">NeoChat es una aplicación de chat que le permite aprovechar al máximo la red Matrix. Le proporciona un modo seguro de enviar mensajes de texto, vídeos y archivos de sonido a su familia, colegas y amigos.</p>
|
||||
@@ -101,6 +92,7 @@
|
||||
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
|
||||
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
|
||||
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
|
||||
<p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p>
|
||||
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
|
||||
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p>
|
||||
@@ -111,6 +103,8 @@
|
||||
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
|
||||
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="de">NeoChat versucht eine vollumfängliche Anwendung für die Spezifikation von Matrix zu sein. Damit wird alles der aktuellen stabilen Spezifikation mit den erwähnenswerten Ausnahmen von VoIP, Diskussionsfäden und ein paar Teilen der Ende-zu-Ende-Verschlüsselung unterstützt. Zudem sind andere kleinere Auslassungen vorhanden, da sich die Matrixspezifikation ständig weiterentwickelt. Nichtsdestotrotz soll letztendlich die gesamte Spezifikation unterstützt werden.</p>
|
||||
<p xml:lang="el">Το NeoChat στοχεύει να είναι μια πλήρως εξοπλισμένη εφαρμογή για τις προδιαγραφές Matrix. Ως εκ τούτου, υποστηρίζονται όλα τα στοιχεία της τρέχουσας σταθερής προδιαγραφής με τις αξιοσημείωτες εξαιρέσεις του VoIP, των νημάτων και ορισμένων πτυχών της κρυπτογράφησης στα άκρα. Υπάρχουν μερικές άλλες μικρότερες παραλείψεις που οφείλονται στο γεγονός ότι η προδιαγραφή Matrix εξελίσσεται συνεχώς, αλλά ο στόχος παραμένει να παρέχεται τελικά υποστήριξη για ολόκληρη την προδιαγραφή.</p>
|
||||
<p xml:lang="en-GB">NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
|
||||
<p xml:lang="eo">NeoChat celas esti plene kapabla aplikaĵo por la Matrix-specifo. Kiel tia, ĉio en la nuna stabila specifo kun la rimarkindaj esceptoj de VoIP, fadenoj kaj kelkaj aspektoj de Fin-al-Fina Ĉifrado estas subtenataj. Estas kelkaj aliaj pli malgrandaj preterlasoj pro la fakto, ke la Matrix-speco konstante evoluas, sed la celo restas provizi finfine subtenon por la tuta specifaĵo.</p>
|
||||
<p xml:lang="es">NeoChat pretende ser una aplicación con todas las funciones para la especificación de Matrix. Como tal, admite todo en la especificación estable actual, con las notables excepciones de VoIP, subprocesos y algunas funciones de cifrado de extremo a extremo. Existen algunas omisiones menos importantes debido al hecho de que la especificación de Matrix está en constante evolución, pero el objetivo sigue siendo brindar compatibilidad final con toda la especificación.</p>
|
||||
@@ -140,6 +134,8 @@
|
||||
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
|
||||
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="de">Durch die Weiterentwicklung der Matrix-Spezifikation unterstützt auch NeoChat einige als noch instabil gekennzeichnete Funktionen. Derzeit sind das:</p>
|
||||
<p xml:lang="el">Λόγω της φύσης της ανάπτυξης των προδιαγραφών Matrix, το NeoChat υποστηρίζει επίσης πολλά ασταθή χαρακτηριστικά. Επί του παρόντος, αυτά είναι:</p>
|
||||
<p xml:lang="en-GB">Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
|
||||
<p xml:lang="eo">Pro la naturo de la Matrix-specifevoluo NeoChat ankaŭ subtenas multajn malstabilajn funkciojn. Nuntempe ĉi tiuj estas:</p>
|
||||
<p xml:lang="es">Debido a la naturaleza del desarrollo de la especificación de Matrix, NeoChat también permite numerosas funciones no estables, como:</p>
|
||||
@@ -171,6 +167,7 @@
|
||||
<li xml:lang="ar">التصويت - MSC3381</li>
|
||||
<li xml:lang="ca">Enquestes - MSC3381</li>
|
||||
<li xml:lang="ca-valencia">Enquestes - MSC3381</li>
|
||||
<li xml:lang="el">Δημοσκοπήσεις - MSC3381</li>
|
||||
<li xml:lang="en-GB">Polls - MSC3381</li>
|
||||
<li xml:lang="eo">Enketoj - MSC3381</li>
|
||||
<li xml:lang="es">Encuestas - MSC3381</li>
|
||||
@@ -189,6 +186,7 @@
|
||||
<li xml:lang="nn">Avstemmingar – MSC3381</li>
|
||||
<li xml:lang="pl">Ankiety - MSC3381</li>
|
||||
<li xml:lang="pt">Inquéritos - MSC3381</li>
|
||||
<li xml:lang="ru">Голосования — MSC3381</li>
|
||||
<li xml:lang="sl">Polls - MSC3381</li>
|
||||
<li xml:lang="sv">Polls - MSC3381</li>
|
||||
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
|
||||
@@ -200,6 +198,7 @@
|
||||
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
|
||||
<li xml:lang="ca">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="ca-valencia">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="el">Πακέτα αυτοκόλλητων - MSC2545</li>
|
||||
<li xml:lang="en-GB">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="eo">Glumark-Pakoj - MSC2545</li>
|
||||
<li xml:lang="es">Paquetes de pegatinas - MSC2545</li>
|
||||
@@ -230,6 +229,7 @@
|
||||
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
|
||||
<li xml:lang="ca">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="ca-valencia">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="el">Τοποθεσία γεγονότα - MSC3488</li>
|
||||
<li xml:lang="en-GB">Location Events - MSC3488</li>
|
||||
<li xml:lang="eo">Lokaj Eventoj - MSC3488</li>
|
||||
<li xml:lang="es">Eventos de ubicación - MSC3488</li>
|
||||
@@ -294,6 +294,8 @@
|
||||
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
|
||||
<caption xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="de">Hauptansicht mit Raumliste, Unterhaltung und Raum-Informationen</caption>
|
||||
<caption xml:lang="el">Κύρια προβολή με λίστα δωματίων, συνομιλία και πληροφορίες δωματίων</caption>
|
||||
<caption xml:lang="en-GB">Main view with room list, chat, and room information</caption>
|
||||
<caption xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</caption>
|
||||
<caption xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</caption>
|
||||
@@ -327,6 +329,8 @@
|
||||
<caption xml:lang="ar">اكتشف مجتمعات جديدة مع فضاءات ماتركس</caption>
|
||||
<caption xml:lang="ca">Descobriu comunitats noves amb els espais de Matrix</caption>
|
||||
<caption xml:lang="ca-valencia">Descobriu comunitats noves amb els espais de Matrix</caption>
|
||||
<caption xml:lang="de">Neue Gemeinschaften mit den Umgebungen von Matrix erkunden</caption>
|
||||
<caption xml:lang="el">Ανακαλύψτε νέες κοινότητες με το Matrix Spaces</caption>
|
||||
<caption xml:lang="en-GB">Discover new communities with Matrix Spaces</caption>
|
||||
<caption xml:lang="eo">Malkovru novajn komunumojn per Matrix Spaces</caption>
|
||||
<caption xml:lang="es">Descubra nuevas comunidades con los espacios de Matrix</caption>
|
||||
@@ -343,8 +347,10 @@
|
||||
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
|
||||
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
|
||||
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
|
||||
<caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption>
|
||||
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
|
||||
<caption xml:lang="sv">Upptäck nya gemenskaper med Matrix Spaces</caption>
|
||||
<caption xml:lang="ta">மேட்ரிக்ஸு இடங்களின் மூலம் புதிய சமூகங்களைக் கண்டுபிடிக்கலாம்</caption>
|
||||
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption>
|
||||
<caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption>
|
||||
<caption xml:lang="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
|
||||
@@ -363,6 +369,8 @@
|
||||
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
|
||||
<caption xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="de">Hauptansicht mit Raumliste, Unterhaltung und Raum-Informationen</caption>
|
||||
<caption xml:lang="el">Κύρια προβολή με λίστα δωματίων, συνομιλία και πληροφορίες δωματίων</caption>
|
||||
<caption xml:lang="en-GB">Main view with room list, chat, and room information</caption>
|
||||
<caption xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</caption>
|
||||
<caption xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</caption>
|
||||
@@ -397,6 +405,8 @@
|
||||
<caption xml:lang="ca">Pantalla d'inici de sessió</caption>
|
||||
<caption xml:lang="ca-valencia">Pantalla d'inici de sessió</caption>
|
||||
<caption xml:lang="cs">Přihlašovací obrazovka</caption>
|
||||
<caption xml:lang="de">Anmeldebildschirm</caption>
|
||||
<caption xml:lang="el">Οθόνη εισόδου</caption>
|
||||
<caption xml:lang="en-GB">Login screen</caption>
|
||||
<caption xml:lang="eo">Ensaluta ekrano</caption>
|
||||
<caption xml:lang="es">Pantalla de inicio de sesión</caption>
|
||||
@@ -429,6 +439,8 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="24.08.3" date="2024-11-07"/>
|
||||
<release version="24.08.2" date="2024-10-10"/>
|
||||
<release version="24.08.1" date="2024-09-12"/>
|
||||
<release version="24.08.0" date="2024-08-22"/>
|
||||
<release version="24.05.2" date="2024-07-04"/>
|
||||
|
||||
1066
po/ar/neochat.po
1066
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
1060
po/ca/neochat.po
1060
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1106
po/cs/neochat.po
1106
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1007
po/da/neochat.po
1007
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
2498
po/de/neochat.po
2498
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1716
po/el/neochat.po
1716
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1054
po/en_GB/neochat.po
1054
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1216
po/eo/neochat.po
1216
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1060
po/es/neochat.po
1060
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1081
po/eu/neochat.po
1081
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1126
po/fi/neochat.po
1126
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1059
po/fr/neochat.po
1059
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1056
po/gl/neochat.po
1056
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
1294
po/hu/neochat.po
1294
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1061
po/ia/neochat.po
1061
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1118
po/id/neochat.po
1118
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1028
po/ie/neochat.po
1028
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1191
po/it/neochat.po
1191
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
944
po/ja/neochat.po
944
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1054
po/ka/neochat.po
1054
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1121
po/ko/neochat.po
1121
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
949
po/lt/neochat.po
949
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1864
po/lv/neochat.po
1864
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1058
po/nl/neochat.po
1058
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1174
po/nn/neochat.po
1174
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1057
po/pa/neochat.po
1057
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1058
po/pl/neochat.po
1058
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1118
po/pt/neochat.po
1118
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1080
po/pt_BR/neochat.po
1080
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
2038
po/ru/neochat.po
2038
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1093
po/sk/neochat.po
1093
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
122
po/sl/docs/neochat/man-neochat.1.docbook
Normal file
122
po/sl/docs/neochat/man-neochat.1.docbook
Normal file
@@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
|
||||
<!ENTITY % Slovenian "INCLUDE">
|
||||
]>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
-->
|
||||
|
||||
<refentry lang="&language;">
|
||||
<refentryinfo>
|
||||
<title
|
||||
>Uporabniški priročnik za NeoChat</title>
|
||||
<author
|
||||
><firstname
|
||||
>Carl</firstname
|
||||
><surname
|
||||
>Schwan</surname
|
||||
> <contrib
|
||||
>Stran z navodili za NeoChat.</contrib
|
||||
> <email
|
||||
>carl@carlschwan.eu</email
|
||||
></author>
|
||||
<date
|
||||
>01.11.2022</date>
|
||||
<releaseinfo
|
||||
>22,09</releaseinfo>
|
||||
<productname
|
||||
>NeoChat</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>
|
||||
<command
|
||||
>neochat</command>
|
||||
</refentrytitle>
|
||||
<manvolnum
|
||||
>1</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname
|
||||
>neochat</refname>
|
||||
<refpurpose
|
||||
>Odjemalec za interakcijo s protokolom za matrično sporočanje</refpurpose>
|
||||
</refnamediv>
|
||||
<!-- body begins here -->
|
||||
<refsynopsisdiv id='synopsis'>
|
||||
<cmdsynopsis
|
||||
><command
|
||||
>neochat</command
|
||||
> <arg choice="opt"
|
||||
><replaceable
|
||||
>URI</replaceable
|
||||
></arg
|
||||
> </cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
|
||||
<refsect1 id="description">
|
||||
<title
|
||||
>Opis</title>
|
||||
<para
|
||||
><command
|
||||
>neochat</command
|
||||
> je aplikacija za klepet za matrični protokol, ki deluje na namizju in mobilni napravi. </para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="options"
|
||||
><title
|
||||
>Možnosti</title>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term
|
||||
><option
|
||||
>URI</option
|
||||
></term>
|
||||
<listitem>
|
||||
<para
|
||||
>Uri matrike za uporabnika ali sobo. npr. matrix:u/user:example.org in matrix:r/root:example.org. Tako bo NeoChat poskušal odpreti dano sobo ali pogovor. </para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="bug">
|
||||
<title
|
||||
>Poročanje o napakah</title>
|
||||
<para
|
||||
>Napake in zahteve po funkcijah lahko prijavite na <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General"
|
||||
>https://bugs.kde.org/enter_bug.cgi? product=NeoChat&component=General</ulink
|
||||
></para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title
|
||||
>Poglej tudi</title>
|
||||
<simplelist>
|
||||
<member
|
||||
>Seznam pogostih vprašanj o Matrix <ulink url="https://matrix.org/faq/"
|
||||
>https://matrix.org/faq/</ulink
|
||||
> </member>
|
||||
<member
|
||||
>kf5options(7)</member>
|
||||
<member
|
||||
>qt5options(7)</member>
|
||||
</simplelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1 id="copyright"
|
||||
><title
|
||||
>Avtorske pravice</title>
|
||||
<para
|
||||
>Avtorske pravice © 2020-2022 Tobias Fella </para>
|
||||
<para
|
||||
>Avtorske pravice © 2020-2022 Carl Schwan </para>
|
||||
<para
|
||||
>Licenca: GNU General Public različica 3 ali novejša <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
>https://www.gnu.org/licenses/gpl-3.0 .html</ulink
|
||||
>></para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
1064
po/sl/neochat.po
1064
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1064
po/sv/neochat.po
1064
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1059
po/ta/neochat.po
1059
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1031
po/tok/neochat.po
1031
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR})
|
||||
1058
po/tr/neochat.po
1058
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1065
po/uk/neochat.po
1065
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1045
po/zh_TW/neochat.po
1045
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
158
snapcraft.yaml
Normal file
158
snapcraft.yaml
Normal file
@@ -0,0 +1,158 @@
|
||||
# SPDX-FileCopyrightText: 2024 Scarlett Moore <sgmoore@kde.org>
|
||||
#
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
---
|
||||
name: neochat
|
||||
base: core22
|
||||
adopt-info: neochat
|
||||
grade: stable
|
||||
confinement: strict
|
||||
apps:
|
||||
neochat:
|
||||
extensions:
|
||||
- kde-neon-6
|
||||
command: usr/bin/neochat
|
||||
common-id: org.kde.neochat
|
||||
desktop: usr/share/applications/org.kde.neochat.desktop
|
||||
plugs:
|
||||
- home
|
||||
- removable-media
|
||||
- audio-playback
|
||||
- unity7
|
||||
- network
|
||||
- network-bind
|
||||
- network-manager-observe
|
||||
- password-manager-service
|
||||
- accounts-service
|
||||
|
||||
compression: lzo
|
||||
|
||||
slots:
|
||||
session-dbus-interface:
|
||||
interface: dbus
|
||||
name: org.kde.neochat
|
||||
bus: session
|
||||
|
||||
parts:
|
||||
olm:
|
||||
source: https://gitlab.matrix.org/matrix-org/olm.git
|
||||
source-depth: 1
|
||||
source-tag: '3.2.12'
|
||||
plugin: cmake
|
||||
cmake-parameters:
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
prime:
|
||||
- -usr/include
|
||||
- -usr/lib/*/pkgconfig
|
||||
- -usr/lib/*/cmake
|
||||
|
||||
libsecret:
|
||||
source: https://gitlab.gnome.org/GNOME/libsecret.git
|
||||
source-tag: '0.21.4'
|
||||
source-depth: 1
|
||||
plugin: meson
|
||||
meson-parameters:
|
||||
- --prefix=/usr
|
||||
- -Doptimization=3
|
||||
- -Ddebug=true
|
||||
- -Dmanpage=false
|
||||
- -Dvapi=false
|
||||
- -Dintrospection=false
|
||||
- -Dcrypto=disabled
|
||||
- -Dgtk_doc=false
|
||||
build-packages:
|
||||
- meson
|
||||
- libglib2.0-dev
|
||||
- libgcrypt20-dev
|
||||
prime:
|
||||
- -usr/include
|
||||
- -usr/lib/*/pkgconfig
|
||||
|
||||
qtkeychain:
|
||||
after: [libsecret]
|
||||
source: https://github.com/frankosterfeld/qtkeychain.git
|
||||
source-tag: 0.14.3
|
||||
source-depth: 1
|
||||
plugin: cmake
|
||||
build-environment:
|
||||
- PKG_CONFIG_PATH: $CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET/pkgconfig:$PKG_CONFIG_PATH
|
||||
cmake-parameters:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DBUILD_TRANSLATIONS=NO
|
||||
- -DBUILD_WITH_QT6=ON
|
||||
prime:
|
||||
- -usr/include
|
||||
- -usr/lib/*/pkgconfig
|
||||
- -usr/lib/*/cmake
|
||||
|
||||
libquotient:
|
||||
after:
|
||||
- olm
|
||||
- qtkeychain
|
||||
source: https://github.com/quotient-im/libQuotient.git
|
||||
source-tag: 0.8.2
|
||||
source-depth: 1
|
||||
plugin: cmake
|
||||
build-packages:
|
||||
- libssl-dev
|
||||
cmake-parameters:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DBUILD_TESTING=OFF
|
||||
- -DQuotient_ENABLE_E2EE=ON
|
||||
- -DBUILD_WITH_QT6=ON
|
||||
prime:
|
||||
- -usr/include
|
||||
- -usr/lib/*/pkgconfig
|
||||
- -usr/lib/*/cmake
|
||||
|
||||
kquickimageeditor:
|
||||
source: https://invent.kde.org/libraries/kquickimageeditor.git
|
||||
source-tag: 'v0.3.0'
|
||||
source-depth: 1
|
||||
plugin: cmake
|
||||
cmake-parameters:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DBUILD_WITH_QT6=ON
|
||||
- -DBUILD_TESTING=OFF
|
||||
prime:
|
||||
- -usr/include
|
||||
- -usr/lib/*/pkgconfig
|
||||
- -usr/lib/*/cmake
|
||||
|
||||
neochat:
|
||||
after:
|
||||
- qtkeychain
|
||||
- libquotient
|
||||
- kquickimageeditor
|
||||
parse-info:
|
||||
- usr/share/metainfo/org.kde.neochat.appdata.xml
|
||||
source: https://invent.kde.org/network/neochat.git
|
||||
source-tag: 'v24.08.1'
|
||||
plugin: cmake
|
||||
build-packages:
|
||||
- cmark
|
||||
- libcmark-dev
|
||||
- libsqlite3-dev
|
||||
- libvulkan-dev
|
||||
- libxkbcommon-dev
|
||||
- libicu-dev
|
||||
- libpulse0
|
||||
cmake-parameters:
|
||||
- -DCMAKE_INSTALL_PREFIX=/usr
|
||||
- -DCMAKE_BUILD_TYPE=Release
|
||||
- -DBUILD_TESTING=OFF
|
||||
prime:
|
||||
- -usr/share/man
|
||||
|
||||
deps:
|
||||
after: [neochat]
|
||||
plugin: nil
|
||||
stage-packages:
|
||||
- libcmark0.30.2
|
||||
prime:
|
||||
- usr/lib/*/libcmark.so*
|
||||
|
||||
@@ -194,6 +194,8 @@ add_library(neochat STATIC
|
||||
neochatroommember.h
|
||||
models/threadmodel.cpp
|
||||
models/threadmodel.h
|
||||
enums/messagetype.h
|
||||
messagecomponent.h
|
||||
)
|
||||
|
||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||
@@ -238,11 +240,8 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/EmojiSas.qml
|
||||
qml/ConfirmDeactivateAccountDialog.qml
|
||||
qml/VerificationCanceled.qml
|
||||
qml/GlobalMenu.qml
|
||||
qml/EditMenu.qml
|
||||
qml/MessageDelegateContextMenu.qml
|
||||
qml/FileDelegateContextMenu.qml
|
||||
qml/FileDelegateContextMenuMobile.qml
|
||||
qml/MessageSourceSheet.qml
|
||||
qml/ConfirmEncryptionDialog.qml
|
||||
qml/RoomSearchPage.qml
|
||||
@@ -308,7 +307,11 @@ add_subdirectory(login)
|
||||
add_subdirectory(chatbar)
|
||||
|
||||
if(NOT ANDROID AND NOT WIN32)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
||||
qt_target_qml_sources(neochat QML_FILES
|
||||
qml/ShareAction.qml
|
||||
qml/GlobalMenu.qml
|
||||
qml/EditMenu.qml
|
||||
)
|
||||
else()
|
||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||
QT_RESOURCE_ALIAS qml/ShareAction.qml
|
||||
@@ -386,7 +389,7 @@ if(NOT ANDROID)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_ICU)
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
|
||||
target_sources(neochat PRIVATE runner.cpp)
|
||||
@@ -442,8 +445,11 @@ if(ANDROID)
|
||||
target_sources(neochat-app PRIVATE notifyrc.qrc)
|
||||
target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL)
|
||||
kirigami_package_breeze_icons(ICONS
|
||||
"arrow-down"
|
||||
"arrow-up"
|
||||
"arrow-down-symbolic"
|
||||
"arrow-up-symbolic"
|
||||
"arrow-up-double-symbolic"
|
||||
"arrow-left-symbolic"
|
||||
"arrow-right-symbolic"
|
||||
"checkmark"
|
||||
"help-about"
|
||||
"im-user"
|
||||
@@ -452,6 +458,7 @@ if(ANDROID)
|
||||
"mail-attachment"
|
||||
"dialog-cancel"
|
||||
"preferences-desktop-emoticons"
|
||||
"preferences-security"
|
||||
"document-open"
|
||||
"document-save"
|
||||
"document-send"
|
||||
|
||||
@@ -1,70 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "actionshandler.h"
|
||||
|
||||
#include <Quotient/csapi/joining.h>
|
||||
#include <Quotient/events/roommemberevent.h>
|
||||
|
||||
#include <cmark.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <QStringBuilder>
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
ActionsHandler::ActionsHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
void ActionsHandler::handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache)
|
||||
{
|
||||
}
|
||||
|
||||
NeoChatRoom *ActionsHandler::room() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void ActionsHandler::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (m_room == room) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
|
||||
{
|
||||
if (!m_room || !chatBarCache) {
|
||||
if (room == nullptr || chatBarCache == nullptr) {
|
||||
qWarning() << "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.";
|
||||
return;
|
||||
}
|
||||
|
||||
checkEffects(chatBarCache->text());
|
||||
if (!chatBarCache->attachmentPath().isEmpty()) {
|
||||
QUrl url(chatBarCache->attachmentPath());
|
||||
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||
m_room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
|
||||
room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
|
||||
chatBarCache->setAttachmentPath({});
|
||||
chatBarCache->setText({});
|
||||
return;
|
||||
}
|
||||
|
||||
QString handledText = chatBarCache->text();
|
||||
handledText = handleMentions(handledText, chatBarCache->mentions());
|
||||
handleMessage(chatBarCache->text(), handledText, chatBarCache);
|
||||
const auto handledText = handleMentions(chatBarCache);
|
||||
const auto result = handleQuickEdit(room, handledText);
|
||||
if (!result) {
|
||||
handleMessage(room, handledText, chatBarCache);
|
||||
}
|
||||
}
|
||||
|
||||
QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *mentions)
|
||||
QString ActionsHandler::handleMentions(ChatBarCache *chatBarCache)
|
||||
{
|
||||
const auto mentions = chatBarCache->mentions();
|
||||
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
|
||||
return a.cursor.anchor() > b.cursor.anchor();
|
||||
});
|
||||
|
||||
auto handledText = chatBarCache->text();
|
||||
for (const auto &mention : *mentions) {
|
||||
if (mention.text.isEmpty() || mention.id.isEmpty()) {
|
||||
continue;
|
||||
@@ -78,48 +56,68 @@ QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *ment
|
||||
return handledText;
|
||||
}
|
||||
|
||||
void ActionsHandler::handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache)
|
||||
bool ActionsHandler::handleQuickEdit(NeoChatRoom *room, const QString &handledText)
|
||||
{
|
||||
Q_ASSERT(m_room);
|
||||
if (room == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
|
||||
auto match = sed.match(text);
|
||||
auto match = sed.match(handledText);
|
||||
if (match.hasMatch()) {
|
||||
const QString regex = match.captured(1);
|
||||
const QString replacement = match.captured(2).toHtmlEscaped();
|
||||
const QString flags = match.captured(3);
|
||||
|
||||
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
|
||||
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
|
||||
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
|
||||
if (event->senderId() == m_room->localMember().id() && event->hasTextContent()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event->senderId() == room->localMember().id() && event->has<EventContent::TextContent>()) {
|
||||
#else
|
||||
if (event->senderId() == room->localMember().id() && event->hasTextContent()) {
|
||||
#endif
|
||||
QString originalString;
|
||||
if (event->content()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
originalString = event->get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
|
||||
#endif
|
||||
} else {
|
||||
originalString = event->plainBody();
|
||||
}
|
||||
if (flags == "/g"_ls) {
|
||||
m_room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
|
||||
if (flags == "/g"_L1) {
|
||||
room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
|
||||
} else {
|
||||
m_room->postHtmlMessage(handledText,
|
||||
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
||||
event->msgtype(),
|
||||
{},
|
||||
event->id());
|
||||
room->postHtmlMessage(handledText,
|
||||
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
||||
event->msgtype(),
|
||||
{},
|
||||
event->id());
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActionsHandler::handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto messageType = RoomMessageEvent::MsgType::Text;
|
||||
|
||||
if (handledText.startsWith(QLatin1Char('/'))) {
|
||||
for (const auto &action : ActionsModel::instance().allActions()) {
|
||||
if (handledText.indexOf(action.prefix) == 1
|
||||
&& (handledText.indexOf(" "_ls) == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
|
||||
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room, chatBarCache);
|
||||
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), room, chatBarCache);
|
||||
if (action.messageType.has_value()) {
|
||||
messageType = *action.messageType;
|
||||
}
|
||||
@@ -136,35 +134,11 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
|
||||
textHandler.setData(handledText);
|
||||
handledText = textHandler.handleSendText();
|
||||
|
||||
if (handledText.count("<p>"_ls) == 1 && handledText.count("</p>"_ls) == 1) {
|
||||
handledText.remove("<p>"_ls);
|
||||
handledText.remove("</p>"_ls);
|
||||
}
|
||||
|
||||
if (handledText.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_room->postMessage(text, handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
|
||||
}
|
||||
|
||||
void ActionsHandler::checkEffects(const QString &text)
|
||||
{
|
||||
std::optional<QString> effect = std::nullopt;
|
||||
if (text.contains(QStringLiteral("\u2744"))) {
|
||||
effect = QLatin1String("snowflake");
|
||||
} else if (text.contains(QStringLiteral("\u1F386"))) {
|
||||
effect = QLatin1String("fireworks");
|
||||
} else if (text.contains(QStringLiteral("\u2F387"))) {
|
||||
effect = QLatin1String("fireworks");
|
||||
} else if (text.contains(QStringLiteral("\u1F389"))) {
|
||||
effect = QLatin1String("confetti");
|
||||
} else if (text.contains(QStringLiteral("\u1F38A"))) {
|
||||
effect = QLatin1String("confetti");
|
||||
}
|
||||
if (effect.has_value()) {
|
||||
Q_EMIT showEffect(*effect);
|
||||
}
|
||||
room->postMessage(chatBarCache->text(), handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
|
||||
}
|
||||
|
||||
#include "moc_actionshandler.cpp"
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "neochatroom.h"
|
||||
#include <QString>
|
||||
|
||||
class ChatBarCache;
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ActionsHandler
|
||||
*
|
||||
* This class handles chat messages ready for posting to a room.
|
||||
* This class contains functions to handle chat messages ready for posting to a room.
|
||||
*
|
||||
* Everything that needs to be done to prepare the message for posting in a room
|
||||
* including:
|
||||
@@ -31,36 +27,17 @@ class NeoChatRoom;
|
||||
*
|
||||
* @sa ActionsModel, NeoChatRoom
|
||||
*/
|
||||
class ActionsHandler : public QObject
|
||||
class ActionsHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The room that messages will be sent to.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
explicit ActionsHandler(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void showEffect(const QString &effect);
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* @brief Pre-process text and send message event.
|
||||
*/
|
||||
void handleMessageEvent(ChatBarCache *chatBarCache);
|
||||
static void handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache);
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
void checkEffects(const QString &text);
|
||||
static QString handleMentions(ChatBarCache *chatBarCache);
|
||||
static bool handleQuickEdit(NeoChatRoom *room, const QString &handledText);
|
||||
|
||||
QString handleMentions(QString handledText, QList<Mention> *mentions);
|
||||
void handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache);
|
||||
static void handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache);
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@ ecm_add_qml_module(chatbar GENERATE_PLUGIN_SOURCE
|
||||
CompletionMenu.qml
|
||||
EmojiDelegate.qml
|
||||
EmojiGrid.qml
|
||||
ReplyPane.qml
|
||||
PieProgressBar.qml
|
||||
EmojiPicker.qml
|
||||
EmojiDialog.qml
|
||||
|
||||
@@ -53,14 +53,6 @@ QQC2.Control {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The ActionsHandler object to use.
|
||||
*
|
||||
* This is expected to have the correct room set otherwise messages will be sent
|
||||
* to the wrong room.
|
||||
*/
|
||||
required property ActionsHandler actionsHandler
|
||||
|
||||
/**
|
||||
* @brief The list of actions in the ChatBar.
|
||||
*
|
||||
@@ -175,6 +167,7 @@ QQC2.Control {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
Layout.preferredHeight: active ? item.implicitHeight : 0
|
||||
|
||||
active: visible
|
||||
visible: root.currentRoom.mainCache.replyId.length > 0 || root.currentRoom.mainCache.attachmentPath.length > 0
|
||||
@@ -361,15 +354,32 @@ QQC2.Control {
|
||||
|
||||
Component {
|
||||
id: replyPane
|
||||
ReplyPane {
|
||||
userName: _private.chatBarCache.relationUser.displayName
|
||||
userColor: _private.chatBarCache.relationUser.color
|
||||
userAvatar: _private.chatBarCache.relationUser.avatarUrl
|
||||
text: _private.chatBarCache.relationMessage
|
||||
Item {
|
||||
implicitWidth: replyComponent.implicitWidth
|
||||
implicitHeight: replyComponent.implicitHeight
|
||||
ReplyComponent {
|
||||
id: replyComponent
|
||||
replyEventId: _private.chatBarCache.replyId
|
||||
replyAuthor: _private.chatBarCache.relationAuthor
|
||||
replyContentModel: _private.chatBarCache.relationEventContentModel
|
||||
maxContentWidth: paneLoader.item.width
|
||||
}
|
||||
QQC2.Button {
|
||||
id: cancelButton
|
||||
|
||||
onCancel: {
|
||||
_private.chatBarCache.replyId = "";
|
||||
_private.chatBarCache.attachmentPath = "";
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Cancel reply")
|
||||
icon.name: "dialog-close"
|
||||
onClicked: {
|
||||
_private.chatBarCache.replyId = "";
|
||||
_private.chatBarCache.attachmentPath = "";
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,7 +401,7 @@ QQC2.Control {
|
||||
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
|
||||
|
||||
function postMessage() {
|
||||
root.actionsHandler.handleMessageEvent(_private.chatBarCache);
|
||||
_private.chatBarCache.postMessage();
|
||||
repeatTimer.stop();
|
||||
root.currentRoom.markAllMessagesAsRead();
|
||||
textField.clear();
|
||||
|
||||
@@ -33,7 +33,7 @@ QQC2.ItemDelegate {
|
||||
Kirigami.Icon {
|
||||
width: Kirigami.Units.gridUnit * 0.5
|
||||
height: Kirigami.Units.gridUnit * 0.5
|
||||
source: "arrow-down"
|
||||
source: "arrow-down-symbolic"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
visible: root.showTones
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
property string userName
|
||||
property color userColor
|
||||
property url userAvatar: ""
|
||||
property var text
|
||||
|
||||
signal cancel
|
||||
|
||||
Rectangle {
|
||||
id: verticalBorder
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
implicitWidth: Kirigami.Units.smallSpacing
|
||||
color: userColor
|
||||
}
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
KirigamiComponents.Avatar {
|
||||
id: replyAvatar
|
||||
|
||||
implicitWidth: Kirigami.Units.iconSizes.small
|
||||
implicitHeight: Kirigami.Units.iconSizes.small
|
||||
|
||||
source: userAvatar
|
||||
name: userName
|
||||
color: userColor
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
color: userColor
|
||||
text: userName
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
QQC2.TextArea {
|
||||
id: textArea
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
text: "<style> a{color:" + Kirigami.Theme.linkColor + ";}.user-pill{}</style>" + replyTextMetrics.elidedText
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
readOnly: true
|
||||
wrapMode: TextEdit.Wrap
|
||||
textFormat: TextEdit.RichText
|
||||
background: Item {}
|
||||
HoverHandler {
|
||||
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: replyTextMetrics
|
||||
|
||||
text: root.text
|
||||
font: textArea.font
|
||||
elide: Qt.ElideRight
|
||||
elideWidth: textArea.width * 2 - Kirigami.Units.smallSpacing * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
id: cancelButton
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Cancel reply")
|
||||
icon.name: "dialog-close"
|
||||
onClicked: {
|
||||
root.cancel();
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
#include "actionshandler.h"
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "eventhandler.h"
|
||||
#include "neochatroom.h"
|
||||
@@ -53,6 +54,7 @@ void ChatBarCache::setReplyId(const QString &replyId)
|
||||
m_relationType = Reply;
|
||||
}
|
||||
m_attachmentPath = QString();
|
||||
delete m_relationContentModel;
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
Q_EMIT attachmentPathChanged();
|
||||
}
|
||||
@@ -82,11 +84,12 @@ void ChatBarCache::setEditId(const QString &editId)
|
||||
m_relationType = Edit;
|
||||
}
|
||||
m_attachmentPath = QString();
|
||||
delete m_relationContentModel;
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
Q_EMIT attachmentPathChanged();
|
||||
}
|
||||
|
||||
Quotient::RoomMember ChatBarCache::relationUser() const
|
||||
Quotient::RoomMember ChatBarCache::relationAuthor() const
|
||||
{
|
||||
if (parent() == nullptr) {
|
||||
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
|
||||
@@ -124,6 +127,28 @@ QString ChatBarCache::relationMessage() const
|
||||
return {};
|
||||
}
|
||||
|
||||
MessageContentModel *ChatBarCache::relationEventContentModel()
|
||||
{
|
||||
if (parent() == nullptr) {
|
||||
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
|
||||
return nullptr;
|
||||
}
|
||||
if (m_relationId.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (m_relationContentModel != nullptr) {
|
||||
return m_relationContentModel;
|
||||
}
|
||||
|
||||
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||
if (room == nullptr) {
|
||||
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
|
||||
return nullptr;
|
||||
}
|
||||
m_relationContentModel = new MessageContentModel(room, m_relationId, true);
|
||||
return m_relationContentModel;
|
||||
}
|
||||
|
||||
bool ChatBarCache::isThreaded() const
|
||||
{
|
||||
return !m_threadId.isEmpty();
|
||||
@@ -156,6 +181,7 @@ void ChatBarCache::setAttachmentPath(const QString &attachmentPath)
|
||||
m_attachmentPath = attachmentPath;
|
||||
m_relationType = None;
|
||||
const auto oldEventId = std::exchange(m_relationId, QString());
|
||||
delete m_relationContentModel;
|
||||
Q_EMIT attachmentPathChanged();
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
}
|
||||
@@ -165,6 +191,7 @@ void ChatBarCache::clearRelations()
|
||||
const auto oldEventId = std::exchange(m_relationId, QString());
|
||||
const auto oldThreadId = std::exchange(m_threadId, QString());
|
||||
m_attachmentPath = QString();
|
||||
delete m_relationContentModel;
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
Q_EMIT threadIdChanged(oldThreadId, m_threadId);
|
||||
Q_EMIT attachmentPathChanged();
|
||||
@@ -233,4 +260,15 @@ void ChatBarCache::setSavedText(const QString &savedText)
|
||||
m_savedText = savedText;
|
||||
}
|
||||
|
||||
void ChatBarCache::postMessage()
|
||||
{
|
||||
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||
if (room == nullptr) {
|
||||
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
|
||||
return;
|
||||
}
|
||||
|
||||
ActionsHandler::handleMessageEvent(room, this);
|
||||
}
|
||||
|
||||
#include "moc_chatbarcache.cpp"
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextCursor>
|
||||
|
||||
#include "models/messagecontentmodel.h"
|
||||
|
||||
class ChatDocumentHandler;
|
||||
|
||||
namespace Quotient
|
||||
@@ -100,7 +102,7 @@ class ChatBarCache : public QObject
|
||||
*
|
||||
* @sa Quotient::RoomMember
|
||||
*/
|
||||
Q_PROPERTY(Quotient::RoomMember relationUser READ relationUser NOTIFY relationIdChanged)
|
||||
Q_PROPERTY(Quotient::RoomMember relationAuthor READ relationAuthor NOTIFY relationIdChanged)
|
||||
|
||||
/**
|
||||
* @brief The content of the related message.
|
||||
@@ -109,6 +111,13 @@ class ChatBarCache : public QObject
|
||||
*/
|
||||
Q_PROPERTY(QString relationMessage READ relationMessage NOTIFY relationIdChanged)
|
||||
|
||||
/**
|
||||
* @brief The MessageContentModel for the related message.
|
||||
*
|
||||
* Will be nullptr if no related message.
|
||||
*/
|
||||
Q_PROPERTY(MessageContentModel *relationEventContentModel READ relationEventContentModel NOTIFY relationIdChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the chat bar is replying in a thread.
|
||||
*/
|
||||
@@ -154,9 +163,10 @@ public:
|
||||
QString editId() const;
|
||||
void setEditId(const QString &editId);
|
||||
|
||||
Quotient::RoomMember relationUser() const;
|
||||
Quotient::RoomMember relationAuthor() const;
|
||||
|
||||
QString relationMessage() const;
|
||||
MessageContentModel *relationEventContentModel();
|
||||
|
||||
bool isThreaded() const;
|
||||
QString threadId() const;
|
||||
@@ -192,6 +202,11 @@ public:
|
||||
*/
|
||||
void setSavedText(const QString &savedText);
|
||||
|
||||
/**
|
||||
* @brief Post the contents of the cache as a message in the room.
|
||||
*/
|
||||
Q_INVOKABLE void postMessage();
|
||||
|
||||
Q_SIGNALS:
|
||||
void textChanged();
|
||||
void relationIdChanged(const QString &oldEventId, const QString &newEventId);
|
||||
@@ -206,4 +221,6 @@ private:
|
||||
QString m_attachmentPath = QString();
|
||||
QList<Mention> m_mentions;
|
||||
QString m_savedText;
|
||||
|
||||
QPointer<MessageContentModel> m_relationContentModel;
|
||||
};
|
||||
|
||||
@@ -28,8 +28,7 @@ void ColorSchemer::apply(int idx)
|
||||
|
||||
int ColorSchemer::indexForCurrentScheme()
|
||||
{
|
||||
return -1;
|
||||
// return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||
return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||
}
|
||||
|
||||
#include "moc_colorschemer.cpp"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <Quotient/settings.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "proxycontroller.h"
|
||||
@@ -168,6 +169,10 @@ void Controller::addConnection(NeoChatConnection *c)
|
||||
dropConnection(c);
|
||||
});
|
||||
connect(c, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
|
||||
connect(c, &NeoChatConnection::syncDone, this, [this, c]() {
|
||||
m_notificationsManager.handleNotifications(c);
|
||||
});
|
||||
connect(c, &NeoChatConnection::showInviteNotification, &m_notificationsManager, &NotificationsManager::postInviteNotification);
|
||||
|
||||
c->sync();
|
||||
|
||||
@@ -178,6 +183,8 @@ void Controller::dropConnection(NeoChatConnection *c)
|
||||
{
|
||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
|
||||
|
||||
c->disconnect(this);
|
||||
c->disconnect(&m_notificationsManager);
|
||||
m_accountRegistry.drop(c);
|
||||
Q_EMIT connectionDropped(c);
|
||||
}
|
||||
@@ -223,9 +230,6 @@ void Controller::invokeLogin()
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
});
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error), {});
|
||||
});
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection->assumeIdentity(account.userId(), account.deviceId(), accessToken);
|
||||
#else
|
||||
@@ -250,17 +254,17 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QStrin
|
||||
|
||||
switch (job->error()) {
|
||||
case QKeychain::EntryNotFound:
|
||||
Q_EMIT errorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
|
||||
Q_EMIT errorOccured(i18n("Access token wasn't found: Maybe it was deleted?"));
|
||||
break;
|
||||
case QKeychain::AccessDeniedByUser:
|
||||
case QKeychain::AccessDenied:
|
||||
Q_EMIT errorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
|
||||
Q_EMIT errorOccured(i18n("Access to keychain was denied: Please allow NeoChat to read the access token"));
|
||||
break;
|
||||
case QKeychain::NoBackendAvailable:
|
||||
Q_EMIT errorOccured(i18n("No keychain available."), i18n("Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
|
||||
Q_EMIT errorOccured(i18n("No keychain available: Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
|
||||
break;
|
||||
case QKeychain::OtherError:
|
||||
Q_EMIT errorOccured(i18n("Unable to read access token"), job->errorString());
|
||||
Q_EMIT errorOccured(i18n("Unable to read access token: %1", job->errorString()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -325,11 +329,18 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_connection != nullptr) {
|
||||
m_connection->disconnect(this);
|
||||
m_connection->disconnect(&m_notificationsManager);
|
||||
}
|
||||
|
||||
m_connection = connection;
|
||||
|
||||
if (m_connection != nullptr) {
|
||||
m_connection->refreshBadgeNotificationCount();
|
||||
updateBadgeNotificationCount(m_connection, m_connection->badgeNotificationCount());
|
||||
|
||||
connect(m_connection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
|
||||
}
|
||||
|
||||
Q_EMIT activeConnectionChanged(m_connection);
|
||||
@@ -344,7 +355,7 @@ void Controller::listenForNotifications()
|
||||
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
|
||||
|
||||
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
|
||||
NotificationsManager::instance().postPushNotification(data);
|
||||
instance().m_notificationsManager.postPushNotification(data);
|
||||
timer->stop();
|
||||
});
|
||||
|
||||
@@ -356,6 +367,11 @@ void Controller::listenForNotifications()
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::clearInvitationNotification(const QString &roomId)
|
||||
{
|
||||
m_notificationsManager.clearInvitationNotification(roomId);
|
||||
}
|
||||
|
||||
void Controller::updateBadgeNotificationCount(NeoChatConnection *connection, int count)
|
||||
{
|
||||
if (connection == m_connection) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include <Quotient/accountregistry.h>
|
||||
|
||||
class TrayIcon;
|
||||
@@ -53,16 +54,6 @@ class Controller : public QObject
|
||||
Q_PROPERTY(bool csSupported READ csSupported CONSTANT)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Define the types on inline messages that can be shown.
|
||||
*/
|
||||
enum MessageType {
|
||||
Positive, /**< Positive message, typically green. */
|
||||
Info, /**< Info message, typically highlight color. */
|
||||
Error, /**< Error message, typically red. */
|
||||
};
|
||||
Q_ENUM(MessageType)
|
||||
|
||||
static Controller &instance();
|
||||
static Controller *create(QQmlEngine *engine, QJSEngine *)
|
||||
{
|
||||
@@ -98,6 +89,13 @@ public:
|
||||
*/
|
||||
static void listenForNotifications();
|
||||
|
||||
/**
|
||||
* @brief Clear an existing invite notification for the given room.
|
||||
*
|
||||
* Nothing happens if the given room doesn't have an invite notification.
|
||||
*/
|
||||
Q_INVOKABLE void clearInvitationNotification(const QString &roomId);
|
||||
|
||||
Q_INVOKABLE QString loadFileContent(const QString &path) const;
|
||||
|
||||
Quotient::AccountRegistry &accounts();
|
||||
@@ -133,16 +131,20 @@ private:
|
||||
QString m_endpoint;
|
||||
QStringList m_shownImages;
|
||||
|
||||
NotificationsManager m_notificationsManager;
|
||||
|
||||
private Q_SLOTS:
|
||||
void invokeLogin();
|
||||
void setQuitOnLastWindowClosed();
|
||||
void updateBadgeNotificationCount(NeoChatConnection *connection, int count);
|
||||
|
||||
Q_SIGNALS:
|
||||
void errorOccured(const QString &error, const QString &detail);
|
||||
/**
|
||||
* @brief Request a error message be shown to the user.
|
||||
*/
|
||||
void errorOccured(const QString &error);
|
||||
void connectionAdded(NeoChatConnection *connection);
|
||||
void connectionDropped(NeoChatConnection *connection);
|
||||
void activeConnectionChanged(NeoChatConnection *connection);
|
||||
void accountsLoadingChanged();
|
||||
void showMessage(MessageType messageType, const QString &message);
|
||||
};
|
||||
|
||||
31
src/enums/messagetype.h
Normal file
31
src/enums/messagetype.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// 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 <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
/**
|
||||
* @class MessageType
|
||||
*
|
||||
* This class is designed to define the MessageType enumeration.
|
||||
*/
|
||||
class MessageType : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief The types of messages that can be shown.
|
||||
*/
|
||||
enum Type {
|
||||
Information = 0, /**< Info message, typically highlight color. */
|
||||
Positive, /**< Positive message, typically green. */
|
||||
Warning, /**< Warning message, typically amber. */
|
||||
Error, /**< Error message, typically red. */
|
||||
};
|
||||
Q_ENUM(Type);
|
||||
};
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <Quotient/events/encryptionevent.h>
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/events/eventcontent.h>
|
||||
#include <Quotient/events/reactionevent.h>
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/roomavatarevent.h>
|
||||
@@ -33,6 +34,21 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
namespace
|
||||
{
|
||||
enum MemberChange {
|
||||
None = 0,
|
||||
AddName = 1,
|
||||
Rename = 2,
|
||||
RemoveName = 4,
|
||||
AddAvatar = 8,
|
||||
UpdateAvatar = 16,
|
||||
RemoveAvatar = 32,
|
||||
};
|
||||
Q_DECLARE_FLAGS(MemberChanges, MemberChange)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(MemberChanges)
|
||||
};
|
||||
|
||||
QString EventHandler::id(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
@@ -209,10 +225,18 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
{
|
||||
QString body;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (event.hasFileContent()) {
|
||||
#endif
|
||||
// if filename is given or body is equal to filename,
|
||||
// then body is a caption
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString filename = event.get<EventContent::FileContent>()->originalName;
|
||||
#else
|
||||
QString filename = event.content()->fileInfo()->originalName;
|
||||
#endif
|
||||
QString body = event.plainBody();
|
||||
if (filename.isEmpty() || filename == body) {
|
||||
return QString();
|
||||
@@ -220,8 +244,13 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
return body;
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::TextContent>() && event.content()) {
|
||||
body = event.get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
if (event.hasTextContent() && event.content()) {
|
||||
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
||||
#endif
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
}
|
||||
@@ -277,7 +306,7 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
||||
{
|
||||
if (event->isRedacted()) {
|
||||
auto reason = event->redactedBecause()->reason();
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>") : i18n("<i>[This message was deleted: %1]</i>", reason);
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>") : i18n("<i>[This message was deleted: %1]</i>", reason.toHtmlEscaped());
|
||||
}
|
||||
|
||||
const bool prettyPrint = (format == Qt::RichText);
|
||||
@@ -292,7 +321,7 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
||||
},
|
||||
[room, prettyPrint](const RoomMemberEvent &e) {
|
||||
// FIXME: Rewind to the name that was at the time of this event
|
||||
auto subjectName = room->member(e.userId()).htmlSafeDisplayName();
|
||||
auto subjectName = prettyPrint ? room->member(e.userId()).htmlSafeDisplayName() : room->member(e.userId()).displayName();
|
||||
if (e.membership() == Membership::Leave) {
|
||||
if (e.prevContent() && e.prevContent()->displayName) {
|
||||
subjectName = sanitized(*e.prevContent()->displayName);
|
||||
@@ -448,11 +477,16 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
{
|
||||
TextHandler textHandler;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::FileContent>()) {
|
||||
QString fileCaption = event.get<EventContent::FileContent>()->originalName;
|
||||
#else
|
||||
if (event.hasFileContent()) {
|
||||
auto fileCaption = event.content()->fileInfo()->originalName;
|
||||
QString fileCaption = event.content()->fileInfo()->originalName;
|
||||
#endif
|
||||
if (fileCaption.isEmpty()) {
|
||||
fileCaption = event.plainBody();
|
||||
} else if (event.content()->fileInfo()->originalName != event.plainBody()) {
|
||||
} else if (fileCaption != event.plainBody()) {
|
||||
fileCaption = event.plainBody() + " | "_ls + fileCaption;
|
||||
}
|
||||
textHandler.setData(fileCaption);
|
||||
@@ -460,8 +494,13 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
}
|
||||
|
||||
QString body;
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::TextContent>() && event.content()) {
|
||||
body = event.get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
if (event.hasTextContent() && event.content()) {
|
||||
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
||||
#endif
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
}
|
||||
@@ -482,8 +521,12 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
}
|
||||
}
|
||||
|
||||
QString EventHandler::genericBody(const Quotient::RoomEvent *event)
|
||||
QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "genericBody called with room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "genericBody called with event set to nullptr.";
|
||||
return {};
|
||||
@@ -492,123 +535,149 @@ QString EventHandler::genericBody(const Quotient::RoomEvent *event)
|
||||
return i18n("<i>[This message was deleted]</i>");
|
||||
}
|
||||
|
||||
const auto sender = room->member(event->senderId());
|
||||
const auto senderString = QStringLiteral("<a href=\"https://matrix.to/#/%1\">%2</a>").arg(sender.id(), sender.htmlSafeDisplayName());
|
||||
|
||||
return switchOnType(
|
||||
*event,
|
||||
[](const RoomMessageEvent &e) {
|
||||
Q_UNUSED(e)
|
||||
return i18n("sent a message");
|
||||
[senderString](const RoomMessageEvent &) {
|
||||
return i18n("%1 sent a message", senderString);
|
||||
},
|
||||
[](const StickerEvent &e) {
|
||||
Q_UNUSED(e)
|
||||
return i18n("sent a sticker");
|
||||
[senderString](const StickerEvent &) {
|
||||
return i18n("%1 sent a sticker", senderString);
|
||||
},
|
||||
[](const RoomMemberEvent &e) {
|
||||
[senderString](const RoomMemberEvent &e) {
|
||||
switch (e.membership()) {
|
||||
case Membership::Invite:
|
||||
if (e.repeatsState()) {
|
||||
return i18n("reinvited someone to the room");
|
||||
return i18n("%1 reinvited someone to the room", senderString);
|
||||
}
|
||||
Q_FALLTHROUGH();
|
||||
case Membership::Join: {
|
||||
QString text{};
|
||||
// Part 1: invites and joins
|
||||
if (e.repeatsState()) {
|
||||
text = i18n("joined the room (repeated)");
|
||||
return i18n("%1 joined the room (repeated)", senderString);
|
||||
} else if (e.changesMembership()) {
|
||||
text = e.membership() == Membership::Invite ? i18n("invited someone to the room") : i18n("joined the room");
|
||||
}
|
||||
if (!text.isEmpty()) {
|
||||
return text;
|
||||
return e.membership() == Membership::Invite ? i18n("%1 invited someone to the room", senderString)
|
||||
: i18n("%1 joined the room", senderString);
|
||||
}
|
||||
|
||||
// Part 2: profile changes of joined members
|
||||
MemberChanges changes = None;
|
||||
if (e.isRename()) {
|
||||
if (!e.newDisplayName()) {
|
||||
text = i18nc("their refers to a singular user", "cleared their display name");
|
||||
changes |= RemoveName;
|
||||
} else if (!e.prevContent()->displayName) {
|
||||
changes |= AddName;
|
||||
} else {
|
||||
text = i18nc("their refers to a singular user", "changed their display name");
|
||||
changes |= Rename;
|
||||
}
|
||||
}
|
||||
if (e.isAvatarUpdate()) {
|
||||
if (!text.isEmpty()) {
|
||||
text += i18n(" and ");
|
||||
}
|
||||
if (!e.newAvatarUrl()) {
|
||||
text += i18nc("their refers to a singular user", "cleared their avatar");
|
||||
changes |= RemoveAvatar;
|
||||
} else if (!e.prevContent()->avatarUrl) {
|
||||
text += i18n("set an avatar");
|
||||
changes |= AddAvatar;
|
||||
} else {
|
||||
text += i18nc("their refers to a singular user", "updated their avatar");
|
||||
changes |= UpdateAvatar;
|
||||
}
|
||||
}
|
||||
if (text.isEmpty()) {
|
||||
text = i18nc("<user> changed nothing", "changed nothing");
|
||||
|
||||
if (changes.testFlag(AddName)) {
|
||||
if (changes.testFlag(AddAvatar)) {
|
||||
return i18n("%1 set a display name and set an avatar", senderString);
|
||||
} else if (changes.testFlag(UpdateAvatar)) {
|
||||
return i18n("%1 set a display name and updated their avatar", senderString);
|
||||
} else if (changes.testFlag(RemoveAvatar)) {
|
||||
return i18n("%1 set a display name and cleared their avatar", senderString);
|
||||
}
|
||||
return i18n("%1 set a display name for this room", senderString);
|
||||
} else if (changes.testFlag(Rename)) {
|
||||
if (changes.testFlag(AddAvatar)) {
|
||||
return i18n("%1 changed their display name and set an avatar", senderString);
|
||||
} else if (changes.testFlag(UpdateAvatar)) {
|
||||
return i18n("%1 changed their display name and updated their avatar", senderString);
|
||||
} else if (changes.testFlag(RemoveAvatar)) {
|
||||
return i18n("%1 changed their display name and cleared their avatar", senderString);
|
||||
}
|
||||
return i18n("%1 changed their display name", senderString);
|
||||
} else if (changes.testFlag(RemoveName)) {
|
||||
if (changes.testFlag(AddAvatar)) {
|
||||
return i18n("%1 cleared their display name and set an avatar", senderString);
|
||||
} else if (changes.testFlag(UpdateAvatar)) {
|
||||
return i18n("%1 cleared their display name and updated their avatar", senderString);
|
||||
} else if (changes.testFlag(RemoveAvatar)) {
|
||||
return i18n("%1 cleared their display name and cleared their avatar", senderString);
|
||||
}
|
||||
return i18n("%1 cleared their display name", senderString);
|
||||
}
|
||||
return text;
|
||||
|
||||
return i18nc("<user> changed nothing", "%1 changed nothing", senderString);
|
||||
}
|
||||
case Membership::Leave:
|
||||
if (e.prevContent() && e.prevContent()->membership == Membership::Invite) {
|
||||
return (e.senderId() != e.userId()) ? i18n("withdrew a user's invitation") : i18n("rejected the invitation");
|
||||
return (e.senderId() != e.userId()) ? i18n("%1 withdrew a user's invitation", senderString)
|
||||
: i18n("%1 rejected the invitation", senderString);
|
||||
}
|
||||
|
||||
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
|
||||
return (e.senderId() != e.userId()) ? i18n("unbanned a user") : i18n("self-unbanned");
|
||||
return (e.senderId() != e.userId()) ? i18n("%1 unbanned a user", senderString) : i18n("%1 self-unbanned", senderString);
|
||||
}
|
||||
return (e.senderId() != e.userId()) ? i18n("put a user out of the room") : i18n("left the room");
|
||||
return (e.senderId() != e.userId()) ? i18n("%1 put a user out of the room", senderString) : i18n("%1 left the room", senderString);
|
||||
case Membership::Ban:
|
||||
if (e.senderId() != e.userId()) {
|
||||
return i18n("banned a user from the room");
|
||||
return i18n("%1 banned a user from the room", senderString);
|
||||
} else {
|
||||
return i18n("self-banned from the room");
|
||||
return i18n("%1 self-banned from the room", senderString);
|
||||
}
|
||||
case Membership::Knock: {
|
||||
return i18n("requested an invite");
|
||||
return i18n("%1 requested an invite", senderString);
|
||||
}
|
||||
default:;
|
||||
}
|
||||
return i18n("made something unknown");
|
||||
return i18n("%1 made something unknown", senderString);
|
||||
},
|
||||
[](const RoomCanonicalAliasEvent &e) {
|
||||
return (e.alias().isEmpty()) ? i18n("cleared the room main alias") : i18n("set the room main alias");
|
||||
[senderString](const RoomCanonicalAliasEvent &e) {
|
||||
return (e.alias().isEmpty()) ? i18n("%1 cleared the room main alias", senderString) : i18n("%1 set the room main alias", senderString);
|
||||
},
|
||||
[](const RoomNameEvent &e) {
|
||||
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name");
|
||||
[senderString](const RoomNameEvent &e) {
|
||||
return (e.name().isEmpty()) ? i18n("%1 cleared the room name", senderString) : i18n("%1 set the room name", senderString);
|
||||
},
|
||||
[](const RoomTopicEvent &e) {
|
||||
return (e.topic().isEmpty()) ? i18n("cleared the topic") : i18n("set the topic");
|
||||
[senderString](const RoomTopicEvent &e) {
|
||||
return (e.topic().isEmpty()) ? i18n("%1 cleared the topic", senderString) : i18n("%1 set the topic", senderString);
|
||||
},
|
||||
[](const RoomAvatarEvent &) {
|
||||
return i18n("changed the room avatar");
|
||||
[senderString](const RoomAvatarEvent &) {
|
||||
return i18n("%1 changed the room avatar", senderString);
|
||||
},
|
||||
[](const EncryptionEvent &) {
|
||||
return i18n("activated End-to-End Encryption");
|
||||
[senderString](const EncryptionEvent &) {
|
||||
return i18n("%1 activated End-to-End Encryption", senderString);
|
||||
},
|
||||
[](const RoomCreateEvent &e) {
|
||||
return e.isUpgrade() ? i18n("upgraded the room version") : i18n("created the room");
|
||||
[senderString](const RoomCreateEvent &e) {
|
||||
return e.isUpgrade() ? i18n("%1 upgraded the room version", senderString) : i18n("%1 created the room", senderString);
|
||||
},
|
||||
[](const RoomPowerLevelsEvent &) {
|
||||
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
||||
[senderString](const RoomPowerLevelsEvent &) {
|
||||
return i18nc("'power level' means permission level", "%1 changed the power levels for this room", senderString);
|
||||
},
|
||||
[](const LocationBeaconEvent &) {
|
||||
return i18n("sent a live location beacon");
|
||||
[senderString](const LocationBeaconEvent &) {
|
||||
return i18n("%1 sent a live location beacon", senderString);
|
||||
},
|
||||
[](const RoomServerAclEvent &) {
|
||||
return i18n("changed the server access control lists for this room");
|
||||
[senderString](const RoomServerAclEvent &) {
|
||||
return i18n("%1 changed the server access control lists for this room", senderString);
|
||||
},
|
||||
[](const WidgetEvent &e) {
|
||||
[senderString](const WidgetEvent &e) {
|
||||
if (e.fullJson()["unsigned"_ls]["prev_content"_ls].toObject().isEmpty()) {
|
||||
return i18n("added a widget");
|
||||
return i18n("%1 added a widget", senderString);
|
||||
}
|
||||
if (e.contentJson().isEmpty()) {
|
||||
return i18n("removed a widget");
|
||||
return i18n("%1 removed a widget", senderString);
|
||||
}
|
||||
return i18n("configured a widget");
|
||||
return i18n("%1 configured a widget", senderString);
|
||||
},
|
||||
[](const StateEvent &) {
|
||||
return i18n("updated the state");
|
||||
[senderString](const StateEvent &) {
|
||||
return i18n("%1 updated the state", senderString);
|
||||
},
|
||||
[](const PollStartEvent &e) {
|
||||
Q_UNUSED(e);
|
||||
return i18n("started a poll");
|
||||
[senderString](const PollStartEvent &) {
|
||||
return i18n("%1 started a poll", senderString);
|
||||
},
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
@@ -646,32 +715,46 @@ QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Qu
|
||||
// Get the file info for the event.
|
||||
if (event->is<RoomMessageEvent>()) {
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (!roomMessageEvent->has<EventContent::FileContentBase>()) {
|
||||
#else
|
||||
if (!roomMessageEvent->hasFileContent()) {
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
const EventContent::FileInfo *fileInfo;
|
||||
fileInfo = roomMessageEvent->content()->fileInfo();
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, fileInfo, eventId, false, false);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const auto content = roomMessageEvent->get<EventContent::FileContentBase>();
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, content.get(), eventId, false, false);
|
||||
#else
|
||||
const auto content = static_cast<const EventContent::FileContent *>(roomMessageEvent->content());
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, content, eventId, false, false);
|
||||
#endif
|
||||
// if filename isn't specifically given, it is in body
|
||||
// https://spec.matrix.org/latest/client-server-api/#mfile
|
||||
mediaInfo["filename"_ls] = (fileInfo->originalName.isEmpty()) ? roomMessageEvent->plainBody() : fileInfo->originalName;
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["filename"_ls] = content->commonInfo().originalName.isEmpty() ? roomMessageEvent->plainBody() : content->commonInfo().originalName;
|
||||
#else
|
||||
mediaInfo["filename"_ls] = (content->fileInfo()->originalName.isEmpty()) ? roomMessageEvent->plainBody() : content->fileInfo()->originalName;
|
||||
#endif
|
||||
|
||||
return mediaInfo;
|
||||
} else if (event->is<StickerEvent>()) {
|
||||
const EventContent::FileInfo *fileInfo;
|
||||
|
||||
auto stickerEvent = eventCast<const StickerEvent>(event);
|
||||
fileInfo = &stickerEvent->image();
|
||||
auto content = &stickerEvent->image();
|
||||
|
||||
return getMediaInfoFromFileInfo(room, fileInfo, eventId, false, true);
|
||||
return getMediaInfoFromFileInfo(room, content, eventId, false, true);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
const EventContent::FileInfo *fileInfo,
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const Quotient::EventContent::FileContentBase *fileContent,
|
||||
#else
|
||||
const Quotient::EventContent::TypedBase *fileContent,
|
||||
#endif
|
||||
const QString &eventId,
|
||||
bool isThumbnail,
|
||||
bool isSticker)
|
||||
@@ -679,10 +762,18 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
// Get the mxc URL for the media.
|
||||
if (!fileInfo->url().isValid() || fileInfo->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (!fileContent->url().isValid() || fileContent->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
#else
|
||||
if (!fileContent->fileInfo()->url().isValid() || fileContent->fileInfo()->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
#endif
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
QUrl source = room->makeMediaUrl(eventId, fileInfo->url());
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QUrl source = room->makeMediaUrl(eventId, fileContent->url());
|
||||
#else
|
||||
QUrl source = room->makeMediaUrl(eventId, fileContent->fileInfo()->url());
|
||||
#endif
|
||||
|
||||
if (source.isValid()) {
|
||||
mediaInfo["source"_ls] = source;
|
||||
@@ -691,7 +782,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
}
|
||||
}
|
||||
|
||||
auto mimeType = fileInfo->mimeType;
|
||||
auto mimeType = fileContent->type();
|
||||
// Add the MIME type for the media if available.
|
||||
mediaInfo["mimeType"_ls] = mimeType.name();
|
||||
|
||||
@@ -699,45 +790,53 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
mediaInfo["mimeIcon"_ls] = mimeType.iconName();
|
||||
|
||||
// Add media size if available.
|
||||
mediaInfo["size"_ls] = fileInfo->payloadSize;
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["size"_ls] = fileContent->commonInfo().payloadSize;
|
||||
#else
|
||||
mediaInfo["size"_ls] = static_cast<const EventContent::FileContent *>(fileContent)->fileInfo()->payloadSize;
|
||||
#endif
|
||||
|
||||
mediaInfo["isSticker"_ls] = isSticker;
|
||||
|
||||
// Add parameter depending on media type.
|
||||
if (mimeType.name().contains(QStringLiteral("image"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileInfo)) {
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileContent)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["width"_ls] = castInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = castInfo->imageSize.height();
|
||||
#else
|
||||
const auto imageInfo = static_cast<const EventContent::ImageInfo *>(castInfo->fileInfo());
|
||||
mediaInfo["width"_ls] = imageInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = imageInfo->imageSize.height();
|
||||
#endif
|
||||
|
||||
// TODO: Images in certain formats (e.g. WebP) will be erroneously marked as animated, even if they are static.
|
||||
mediaInfo["animated"_ls] = QMovie::supportedFormats().contains(mimeType.preferredSuffix().toUtf8());
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(room, castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
tempInfo = thumbnailInfo;
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromTumbnail(room, castInfo->thumbnail, eventId);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"_ls].toString();
|
||||
if (blurhash.isEmpty()) {
|
||||
tempInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"_ls].toString();
|
||||
if (blurhash.isEmpty()) {
|
||||
tempInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
tempInfo["source"_ls] = QUrl("image://blurhash/"_ls + blurhash);
|
||||
}
|
||||
tempInfo["source"_ls] = QUrl("image://blurhash/"_ls + blurhash);
|
||||
}
|
||||
mediaInfo["tempInfo"_ls] = tempInfo;
|
||||
}
|
||||
mediaInfo["tempInfo"_ls] = tempInfo;
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("video"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::VideoContent *>(fileInfo)) {
|
||||
if (auto castInfo = static_cast<const EventContent::VideoContent *>(fileContent)) {
|
||||
mediaInfo["width"_ls] = castInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = castInfo->imageSize.height();
|
||||
mediaInfo["duration"_ls] = castInfo->duration;
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(room, castInfo->thumbnailInfo(), eventId, true);
|
||||
auto thumbnailInfo = getMediaInfoFromTumbnail(room, castInfo->thumbnail, eventId);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
@@ -753,7 +852,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("audio"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::AudioContent *>(fileInfo)) {
|
||||
if (auto castInfo = static_cast<const EventContent::AudioContent *>(fileContent)) {
|
||||
mediaInfo["duration"_ls] = castInfo->duration;
|
||||
}
|
||||
}
|
||||
@@ -761,6 +860,38 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
return mediaInfo;
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoFromTumbnail(const NeoChatRoom *room, const Quotient::EventContent::Thumbnail &thumbnail, const QString &eventId)
|
||||
{
|
||||
QVariantMap thumbnailInfo;
|
||||
|
||||
if (!thumbnail.url().isValid() || thumbnail.url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
thumbnailInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
QUrl source = room->makeMediaUrl(eventId, thumbnail.url());
|
||||
|
||||
if (source.isValid()) {
|
||||
thumbnailInfo["source"_ls] = source;
|
||||
} else {
|
||||
thumbnailInfo["source"_ls] = QUrl();
|
||||
}
|
||||
}
|
||||
|
||||
auto mimeType = thumbnail.mimeType;
|
||||
// Add the MIME type for the media if available.
|
||||
thumbnailInfo["mimeType"_ls] = mimeType.name();
|
||||
|
||||
// Add the MIME type icon if available.
|
||||
thumbnailInfo["mimeIcon"_ls] = mimeType.iconName();
|
||||
|
||||
// Add media size if available.
|
||||
thumbnailInfo["size"_ls] = thumbnail.payloadSize;
|
||||
|
||||
thumbnailInfo["width"_ls] = thumbnail.imageSize.width();
|
||||
thumbnailInfo["height"_ls] = thumbnail.imageSize.height();
|
||||
|
||||
return thumbnailInfo;
|
||||
}
|
||||
|
||||
bool EventHandler::hasReply(const Quotient::RoomEvent *event, bool showFallbacks)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
|
||||
@@ -192,7 +192,7 @@ public:
|
||||
*
|
||||
* @sa richBody(), plainBody()
|
||||
*/
|
||||
static QString genericBody(const Quotient::RoomEvent *event);
|
||||
static QString genericBody(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Output a string for the event to be used as a RoomList subtitle.
|
||||
@@ -290,8 +290,13 @@ private:
|
||||
|
||||
static QVariantMap getMediaInfoForEvent(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
QVariantMap static getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
const Quotient::EventContent::FileInfo *fileInfo,
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const Quotient::EventContent::FileContentBase *fileContent,
|
||||
#else
|
||||
const Quotient::EventContent::TypedBase *fileContent,
|
||||
#endif
|
||||
const QString &eventId,
|
||||
bool isThumbnail = false,
|
||||
bool isSticker = false);
|
||||
static QVariantMap getMediaInfoFromTumbnail(const NeoChatRoom *room, const Quotient::EventContent::Thumbnail &thumbnail, const QString &eventId);
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@ void LoginHelper::init()
|
||||
m_connection = nullptr;
|
||||
});
|
||||
connect(m_connection, &Connection::networkError, this, [this](QString error, const QString &, int, int) {
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error));
|
||||
Q_EMIT m_connection->errorOccured(i18n("Network Error: %1", std::move(error)));
|
||||
m_isLoggingIn = false;
|
||||
Q_EMIT isLoggingInChanged();
|
||||
});
|
||||
@@ -93,14 +93,14 @@ void LoginHelper::init()
|
||||
if (error == QStringLiteral("Invalid username or password")) {
|
||||
setInvalidPassword(true);
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
Q_EMIT loginErrorOccured(i18n("Login Failed: %1", error));
|
||||
}
|
||||
m_isLoggingIn = false;
|
||||
Q_EMIT isLoggingInChanged();
|
||||
});
|
||||
|
||||
connect(m_connection, &Connection::resolveError, this, [](QString error) {
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error));
|
||||
connect(m_connection, &Connection::resolveError, this, [this](QString error) {
|
||||
Q_EMIT m_connection->errorOccured(i18n("Network Error: %1", std::move(error)));
|
||||
});
|
||||
|
||||
connect(
|
||||
|
||||
@@ -130,7 +130,7 @@ Q_SIGNALS:
|
||||
void loginFlowsChanged();
|
||||
void ssoUrlChanged();
|
||||
void connected();
|
||||
void errorOccured(const QString &message);
|
||||
void loginErrorOccured(const QString &message);
|
||||
void testingChanged();
|
||||
void isLoggingInChanged();
|
||||
void isLoggedInChanged();
|
||||
|
||||
@@ -214,7 +214,7 @@ Kirigami.Page {
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
|
||||
function onErrorOccured(message) {
|
||||
function onLoginErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = message.length > 0;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
@@ -232,7 +232,7 @@ Kirigami.Page {
|
||||
text: root.currentStep.nextAction && root.currentStep.nextAction.text ? root.currentStep.nextAction.text : i18nc("@action:button", "Continue")
|
||||
visible: root.currentStep.nextAction
|
||||
onClicked: root.currentStep.nextAction.trigger()
|
||||
icon.name: "arrow-right"
|
||||
icon.name: "arrow-right-symbolic"
|
||||
enabled: root.currentStep.nextAction ? root.currentStep.nextAction.enabled : false
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ Kirigami.Page {
|
||||
text: i18nc("@action:button", "Go back")
|
||||
visible: root.currentStep.previousAction
|
||||
onClicked: root.currentStep.previousAction.trigger()
|
||||
icon.name: "arrow-left"
|
||||
icon.name: "arrow-left-symbolic"
|
||||
enabled: root.currentStep.previousAction ? root.currentStep.previousAction.enabled : false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
#include "colorschemer.h"
|
||||
#include "controller.h"
|
||||
#include "logger.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "roommanager.h"
|
||||
#include "sharehandler.h"
|
||||
#include "windowcontroller.h"
|
||||
|
||||
22
src/messagecomponent.h
Normal file
22
src/messagecomponent.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// 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 "enums/messagecomponenttype.h"
|
||||
|
||||
struct MessageComponent {
|
||||
MessageComponentType::Type type = MessageComponentType::Other;
|
||||
QString content;
|
||||
QVariantMap attributes;
|
||||
|
||||
int operator==(const MessageComponent &right) const
|
||||
{
|
||||
return type == right.type && content == right.content && attributes == right.attributes;
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return type == MessageComponentType::Other;
|
||||
}
|
||||
};
|
||||
@@ -4,10 +4,11 @@
|
||||
#include "actionsmodel.h"
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "controller.h"
|
||||
#include "enums/messagetype.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
#include <Quotient/events/eventcontent.h>
|
||||
#include <Quotient/events/roommemberevent.h>
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
#include <Quotient/user.h>
|
||||
@@ -24,15 +25,14 @@ QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls
|
||||
|
||||
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18n("Leaving this room."));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18n("Leaving this room."));
|
||||
room->connection()->leaveRoom(room);
|
||||
} else {
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(
|
||||
Controller::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
auto leaving = room->connection()->room(text);
|
||||
@@ -40,10 +40,10 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
|
||||
leaving = room->connection()->roomByAlias(text);
|
||||
}
|
||||
if (leaving) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||
room->connection()->leaveRoom(leaving);
|
||||
} else {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
@@ -51,7 +51,7 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
|
||||
|
||||
auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
} else {
|
||||
room->connection()->user()->rename(text, room);
|
||||
}
|
||||
@@ -193,31 +193,29 @@ QList<ActionsModel::Action> actions{
|
||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||
auto regexMatch = mxidRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
return QString();
|
||||
}
|
||||
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
|
||||
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info,
|
||||
i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information,
|
||||
i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
||||
return QString();
|
||||
}
|
||||
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Ban) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
||||
return QString();
|
||||
}
|
||||
if (room->localMember().id() == text) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18n("You are already in this room."));
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18n("You are already in this room."));
|
||||
return QString();
|
||||
}
|
||||
if (room->joinedMemberIds().contains(text)) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
|
||||
return QString();
|
||||
}
|
||||
room->inviteToRoom(text);
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||
i18nc("<username> was invited into this room", "%1 was invited into this room", text));
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> was invited into this room", "%1 was invited into this room", text));
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -231,9 +229,8 @@ QList<ActionsModel::Action> actions{
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(
|
||||
Controller::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||
@@ -241,7 +238,7 @@ QList<ActionsModel::Action> actions{
|
||||
RoomManager::instance().resolveResource(targetRoom->id());
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
RoomManager::instance().resolveResource(text, "join"_ls);
|
||||
return QString();
|
||||
},
|
||||
@@ -258,9 +255,8 @@ QList<ActionsModel::Action> actions{
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(roomName);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(
|
||||
Controller::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||
@@ -268,7 +264,7 @@ QList<ActionsModel::Action> actions{
|
||||
RoomManager::instance().resolveResource(targetRoom->id());
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
||||
auto connection = dynamic_cast<NeoChatConnection *>(room->connection());
|
||||
const auto knownServer = roomName.mid(roomName.indexOf(":"_ls) + 1);
|
||||
if (parts.length() >= 2) {
|
||||
@@ -289,16 +285,15 @@ QList<ActionsModel::Action> actions{
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(
|
||||
Controller::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
return QString();
|
||||
}
|
||||
if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
RoomManager::instance().resolveResource(text, "join"_ls);
|
||||
return QString();
|
||||
},
|
||||
@@ -327,7 +322,7 @@ QList<ActionsModel::Action> actions{
|
||||
QStringLiteral("nick"),
|
||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
} else {
|
||||
room->connection()->user()->rename(text);
|
||||
}
|
||||
@@ -361,16 +356,15 @@ QList<ActionsModel::Action> actions{
|
||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||
auto regexMatch = mxidRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
return QString();
|
||||
}
|
||||
if (room->connection()->ignoredUsers().contains(text)) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
|
||||
return QString();
|
||||
}
|
||||
room->connection()->addToIgnoredUsers(text);
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
|
||||
|
||||
return QString();
|
||||
},
|
||||
@@ -386,16 +380,15 @@ QList<ActionsModel::Action> actions{
|
||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||
auto regexMatch = mxidRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
return QString();
|
||||
}
|
||||
if (!room->connection()->ignoredUsers().contains(text)) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
|
||||
return QString();
|
||||
}
|
||||
room->connection()->removeFromIgnoredUsers(text);
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -431,14 +424,13 @@ QList<ActionsModel::Action> actions{
|
||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||
auto regexMatch = mxidRegex.match(parts[0]);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
return QString();
|
||||
}
|
||||
auto state = room->currentState().get<RoomMemberEvent>(parts[0]);
|
||||
if (state && state->membership() == Membership::Ban) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info,
|
||||
i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information,
|
||||
i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
|
||||
return QString();
|
||||
}
|
||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||
@@ -446,18 +438,17 @@ QList<ActionsModel::Action> actions{
|
||||
return QString();
|
||||
}
|
||||
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("You are not allowed to ban users from this room."));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("You are not allowed to ban users from this room."));
|
||||
return QString();
|
||||
}
|
||||
if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) {
|
||||
Q_EMIT Controller::instance().showMessage(
|
||||
Controller::Error,
|
||||
Q_EMIT room->showMessage(
|
||||
MessageType::Error,
|
||||
i18nc("You are not allowed to ban <username> from this room.", "You are not allowed to ban %1 from this room.", parts[0]));
|
||||
return QString();
|
||||
}
|
||||
room->ban(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||
i18nc("<username> was banned from this room.", "%1 was banned from this room.", parts[0]));
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> was banned from this room.", "%1 was banned from this room.", parts[0]));
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -472,8 +463,7 @@ QList<ActionsModel::Action> actions{
|
||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||
auto regexMatch = mxidRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
return QString();
|
||||
}
|
||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||
@@ -481,18 +471,16 @@ QList<ActionsModel::Action> actions{
|
||||
return QString();
|
||||
}
|
||||
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("You are not allowed to unban users from this room."));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("You are not allowed to unban users from this room."));
|
||||
return QString();
|
||||
}
|
||||
auto state = room->currentState().get<RoomMemberEvent>(text);
|
||||
if (state && state->membership() != Membership::Ban) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info,
|
||||
i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
|
||||
return QString();
|
||||
}
|
||||
room->unban(text);
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||
i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
|
||||
|
||||
return QString();
|
||||
},
|
||||
@@ -509,16 +497,16 @@ QList<ActionsModel::Action> actions{
|
||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||
auto regexMatch = mxidRegex.match(parts[0]);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0]));
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0]));
|
||||
return QString();
|
||||
}
|
||||
if (parts[0] == room->localMember().id()) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("You cannot kick yourself from the room."));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("You cannot kick yourself from the room."));
|
||||
return QString();
|
||||
}
|
||||
if (!room->isMember(parts[0])) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
|
||||
return QString();
|
||||
}
|
||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||
@@ -527,18 +515,17 @@ QList<ActionsModel::Action> actions{
|
||||
}
|
||||
auto kick = plEvent->kick();
|
||||
if (plEvent->powerLevelForUser(room->localMember().id()) < kick) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("You are not allowed to kick users from this room."));
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("You are not allowed to kick users from this room."));
|
||||
return QString();
|
||||
}
|
||||
if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) {
|
||||
Q_EMIT Controller::instance().showMessage(
|
||||
Controller::Error,
|
||||
Q_EMIT room->showMessage(
|
||||
MessageType::Error,
|
||||
i18nc("You are not allowed to kick <username> from this room", "You are not allowed to kick %1 from this room.", parts[0]));
|
||||
return QString();
|
||||
}
|
||||
room->kickMember(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||
i18nc("<username> was kicked from this room.", "%1 was kicked from this room.", parts[0]));
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> was kicked from this room.", "%1 was kicked from this room.", parts[0]));
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <QImageReader>
|
||||
|
||||
#include <Quotient/events/eventcontent.h>
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
@@ -29,18 +30,6 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply, bool isPending, MessageContentModel *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_room(room)
|
||||
, m_eventId(event != nullptr ? event->id() : QString())
|
||||
, m_eventSenderId(event != nullptr ? event->senderId() : QString())
|
||||
, m_isPending(isPending)
|
||||
, m_isReply(isReply)
|
||||
{
|
||||
intiializeEvent(event);
|
||||
initializeModel();
|
||||
}
|
||||
|
||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply, bool isPending, MessageContentModel *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_room(room)
|
||||
@@ -54,17 +43,137 @@ MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &event
|
||||
void MessageContentModel::initializeModel()
|
||||
{
|
||||
Q_ASSERT(m_room != nullptr);
|
||||
// Allow making a model for an event that is being downloaded but will appear later
|
||||
// e.g. a reply, but we need an ID to know when it has arrived.
|
||||
// Also note that a pending event may not have an event ID yet but as long as we have an event
|
||||
// pointer we can pass out the transaction ID until it is set.
|
||||
Q_ASSERT(!m_eventId.isEmpty() || m_event != nullptr);
|
||||
Q_ASSERT(!m_eventId.isEmpty());
|
||||
|
||||
connect(this, &MessageContentModel::eventUnavailable, this, &MessageContentModel::getEvent);
|
||||
|
||||
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
|
||||
if (m_room != nullptr) {
|
||||
if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) {
|
||||
beginResetModel();
|
||||
m_isPending = false;
|
||||
m_eventId = serverEvent->id();
|
||||
initializeEvent();
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::addedMessages, this, [this](int fromIndex, int toIndex) {
|
||||
if (m_room != nullptr) {
|
||||
for (int i = fromIndex; i <= toIndex; i++) {
|
||||
if (m_room->findInTimeline(i)->event()->id() == m_eventId) {
|
||||
initializeEvent();
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
|
||||
if (m_room != nullptr) {
|
||||
if (m_eventId == newEvent->id()) {
|
||||
beginResetModel();
|
||||
initializeEvent();
|
||||
resetContent();
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) {
|
||||
if (eventId == m_eventId) {
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferProgress, this, [this](const QString &eventId) {
|
||||
if (eventId == m_eventId) {
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
|
||||
if (m_room != nullptr && eventId == m_eventId) {
|
||||
resetContent();
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) {
|
||||
if (eventId == m_eventId) {
|
||||
resetContent();
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room->editCache(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) {
|
||||
if (oldEventId == m_eventId || newEventId == m_eventId) {
|
||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||
beginResetModel();
|
||||
resetContent(newEventId == m_eventId);
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) {
|
||||
if (oldThreadId == m_eventId || newThreadId == m_eventId) {
|
||||
beginResetModel();
|
||||
resetContent(false, newThreadId == m_eventId);
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
|
||||
resetContent();
|
||||
});
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
|
||||
resetContent();
|
||||
});
|
||||
connect(m_room, &Room::memberNameUpdated, this, [this](RoomMember member) {
|
||||
if (m_room != nullptr) {
|
||||
if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
|
||||
if (m_room != nullptr) {
|
||||
if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ThreadsChanged, this, [this]() {
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
});
|
||||
|
||||
initializeEvent();
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
}
|
||||
|
||||
void MessageContentModel::initializeEvent()
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr) {
|
||||
Q_EMIT eventUnavailable();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_eventSenderObject == nullptr) {
|
||||
auto senderId = event->senderId();
|
||||
// A pending event might not have a sender ID set yet but in that case it must
|
||||
// be the local member.
|
||||
if (senderId.isEmpty()) {
|
||||
senderId = m_room->localMember().id();
|
||||
}
|
||||
m_eventSenderObject = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_room, senderId));
|
||||
}
|
||||
Q_EMIT eventUpdated();
|
||||
}
|
||||
|
||||
void MessageContentModel::getEvent()
|
||||
{
|
||||
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
|
||||
if (m_room != nullptr) {
|
||||
if (eventId == m_eventId) {
|
||||
m_notFound = false;
|
||||
intiializeEvent(m_room->getEvent(eventId));
|
||||
initializeEvent();
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
return true;
|
||||
@@ -83,130 +192,7 @@ void MessageContentModel::initializeModel()
|
||||
return false;
|
||||
});
|
||||
|
||||
if (m_event == nullptr) {
|
||||
intiializeEvent(m_room->getEvent(m_eventId));
|
||||
if (m_event == nullptr) {
|
||||
m_room->downloadEventFromServer(m_eventId);
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
|
||||
if (m_room != nullptr && m_event != nullptr) {
|
||||
if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) {
|
||||
beginResetModel();
|
||||
m_isPending = false;
|
||||
intiializeEvent(serverEvent);
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
|
||||
if (m_room != nullptr && m_event != nullptr) {
|
||||
if (m_eventId == newEvent->id()) {
|
||||
beginResetModel();
|
||||
intiializeEvent(newEvent);
|
||||
resetContent();
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) {
|
||||
if (m_event != nullptr && eventId == m_eventId) {
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferProgress, this, [this](const QString &eventId) {
|
||||
if (m_event != nullptr && eventId == m_eventId) {
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
|
||||
if (m_room != nullptr && m_event != nullptr && eventId == m_eventId) {
|
||||
resetContent();
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) {
|
||||
if (m_event != nullptr && eventId == m_eventId) {
|
||||
resetContent();
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room->editCache(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) {
|
||||
if (m_event != nullptr && (oldEventId == m_eventId || newEventId == m_eventId)) {
|
||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||
beginResetModel();
|
||||
resetContent(newEventId == m_eventId);
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) {
|
||||
if (m_event != nullptr && (oldThreadId == m_eventId || newThreadId == m_eventId)) {
|
||||
beginResetModel();
|
||||
resetContent(false, newThreadId == m_eventId);
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
|
||||
resetContent();
|
||||
});
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
|
||||
resetContent();
|
||||
});
|
||||
connect(m_room, &Room::memberNameUpdated, this, [this](RoomMember member) {
|
||||
if (m_room != nullptr && m_event != nullptr) {
|
||||
if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
|
||||
if (m_room != nullptr && m_event != nullptr) {
|
||||
if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ThreadsChanged, this, [this]() {
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
});
|
||||
|
||||
if (m_event != nullptr) {
|
||||
updateReplyModel();
|
||||
}
|
||||
resetModel();
|
||||
}
|
||||
|
||||
void MessageContentModel::intiializeEvent(const QString &eventId)
|
||||
{
|
||||
const auto newEvent = m_room->getEvent(eventId);
|
||||
if (newEvent != nullptr) {
|
||||
intiializeEvent(newEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageContentModel::intiializeEvent(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_event = loadEvent<RoomEvent>(event->fullJson());
|
||||
// a pending event may not previously have had an event ID so update.
|
||||
m_eventId = EventHandler::id(m_event.get());
|
||||
|
||||
auto senderId = m_event->senderId();
|
||||
// A pending event might not have a sender ID set yet but in that case it must
|
||||
// be the local member.
|
||||
if (senderId.isEmpty()) {
|
||||
senderId = m_room->localMember().id();
|
||||
}
|
||||
if (m_eventSenderObject == nullptr) {
|
||||
m_eventSenderObject = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_room, senderId));
|
||||
}
|
||||
Q_EMIT eventUpdated();
|
||||
m_room->downloadEventFromServer(m_eventId);
|
||||
}
|
||||
|
||||
bool MessageContentModel::showAuthor() const
|
||||
@@ -221,7 +207,7 @@ void MessageContentModel::setShowAuthor(bool showAuthor)
|
||||
}
|
||||
m_showAuthor = showAuthor;
|
||||
|
||||
if (m_event != nullptr && m_room->connection()->isIgnored(m_event->senderId())) {
|
||||
if (m_room->connection()->isIgnored(m_eventSenderId)) {
|
||||
if (showAuthor) {
|
||||
beginInsertRows({}, 0, 0);
|
||||
m_components.prepend(MessageComponent{MessageComponentType::Author, QString(), {}});
|
||||
@@ -250,8 +236,23 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
const auto component = m_components[index.row()];
|
||||
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr) {
|
||||
if (role == DisplayRole) {
|
||||
if (m_isReply) {
|
||||
return i18n("Loading reply");
|
||||
} else {
|
||||
return i18n("Loading");
|
||||
}
|
||||
}
|
||||
if (role == ComponentTypeRole) {
|
||||
return component.type;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == DisplayRole) {
|
||||
if (m_notFound || (m_event && m_room->connection()->isIgnored(m_event->senderId()))) {
|
||||
if (m_notFound || m_room->connection()->isIgnored(m_eventSenderId)) {
|
||||
Kirigami::Platform::PlatformTheme *theme =
|
||||
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
|
||||
@@ -265,21 +266,17 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
+ i18nc("@info", "This message was either not found, you do not have permission to view it, or it was sent by an ignored user")
|
||||
+ QStringLiteral("</span>"));
|
||||
}
|
||||
if (component.type == MessageComponentType::Loading && m_isReply) {
|
||||
return i18n("Loading reply");
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
return QString();
|
||||
}
|
||||
if (m_event->isRedacted()) {
|
||||
auto reason = m_event->redactedBecause()->reason();
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
|
||||
: i18n("<i>[This message was deleted: %1]</i>", m_event->redactedBecause()->reason());
|
||||
if (component.type == MessageComponentType::Loading) {
|
||||
if (m_isReply) {
|
||||
return i18n("Loading reply");
|
||||
} else {
|
||||
return i18n("Loading");
|
||||
}
|
||||
}
|
||||
if (!component.content.isEmpty()) {
|
||||
return component.content;
|
||||
}
|
||||
return EventHandler::richBody(m_room, m_event.get());
|
||||
return EventHandler::richBody(m_room, event);
|
||||
}
|
||||
if (role == ComponentTypeRole) {
|
||||
return component.type;
|
||||
@@ -288,53 +285,53 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return component.attributes;
|
||||
}
|
||||
if (role == EventIdRole) {
|
||||
return EventHandler::id(m_event.get());
|
||||
return EventHandler::id(event);
|
||||
}
|
||||
if (role == TimeRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||
return m_event->transactionId() == pendingEvent->transactionId();
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
||||
return event->transactionId() == pendingEvent->transactionId();
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return EventHandler::time(m_event.get(), m_isPending, lastUpdated);
|
||||
return EventHandler::time(event, m_isPending, lastUpdated);
|
||||
}
|
||||
if (role == TimeStringRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||
return m_event->transactionId() == pendingEvent->transactionId();
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
||||
return event->transactionId() == pendingEvent->transactionId();
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return EventHandler::timeString(m_event.get(), QStringLiteral("hh:mm"), m_isPending, lastUpdated);
|
||||
return EventHandler::timeString(event, QStringLiteral("hh:mm"), m_isPending, lastUpdated);
|
||||
}
|
||||
if (role == AuthorRole) {
|
||||
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
|
||||
}
|
||||
if (role == MediaInfoRole) {
|
||||
return EventHandler::mediaInfo(m_room, m_event.get());
|
||||
return EventHandler::mediaInfo(m_room, event);
|
||||
}
|
||||
if (role == FileTransferInfoRole) {
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(m_event.get()));
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(event));
|
||||
}
|
||||
if (role == ItineraryModelRole) {
|
||||
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
||||
}
|
||||
if (role == LatitudeRole) {
|
||||
return EventHandler::latitude(m_event.get());
|
||||
return EventHandler::latitude(event);
|
||||
}
|
||||
if (role == LongitudeRole) {
|
||||
return EventHandler::longitude(m_event.get());
|
||||
return EventHandler::longitude(event);
|
||||
}
|
||||
if (role == AssetRole) {
|
||||
return EventHandler::locationAssetType(m_event.get());
|
||||
return EventHandler::locationAssetType(event);
|
||||
}
|
||||
if (role == PollHandlerRole) {
|
||||
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
|
||||
}
|
||||
if (role == ReplyEventIdRole) {
|
||||
return EventHandler::replyId(m_event.get());
|
||||
return EventHandler::replyId(event);
|
||||
}
|
||||
if (role == ReplyAuthorRole) {
|
||||
return QVariant::fromValue(EventHandler::replyAuthor(m_room, m_event.get()));
|
||||
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event));
|
||||
}
|
||||
if (role == ReplyContentModelRole) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
||||
@@ -390,16 +387,18 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
|
||||
|
||||
void MessageContentModel::resetModel()
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
|
||||
beginResetModel();
|
||||
m_components.clear();
|
||||
|
||||
if ((m_event && m_room->connection()->isIgnored(m_event->senderId())) || m_notFound) {
|
||||
if (m_room->connection()->isIgnored(m_eventSenderId) || m_notFound) {
|
||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_event == nullptr) {
|
||||
if (event == nullptr) {
|
||||
m_components += MessageComponent{MessageComponentType::Loading, QString(), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
@@ -415,8 +414,6 @@ void MessageContentModel::resetModel()
|
||||
|
||||
void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
||||
{
|
||||
Q_ASSERT(m_event != nullptr);
|
||||
|
||||
const auto startRow = m_components[0].type == MessageComponentType::Author ? 1 : 0;
|
||||
beginRemoveRows({}, startRow, rowCount() - 1);
|
||||
m_components.remove(startRow, rowCount() - startRow);
|
||||
@@ -433,15 +430,20 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
||||
|
||||
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<MessageComponent> newComponents;
|
||||
|
||||
if (eventCast<const Quotient::RoomMessageEvent>(m_event)
|
||||
&& eventCast<const Quotient::RoomMessageEvent>(m_event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
||||
if (eventCast<const Quotient::RoomMessageEvent>(event)
|
||||
&& eventCast<const Quotient::RoomMessageEvent>(event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
||||
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
|
||||
return newComponents;
|
||||
}
|
||||
|
||||
if (m_event->isRedacted()) {
|
||||
if (event->isRedacted()) {
|
||||
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
return newComponents;
|
||||
}
|
||||
@@ -453,7 +455,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
if (isEditing) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
} else {
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*m_event.get())));
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event)));
|
||||
}
|
||||
|
||||
if (m_room->urlPreviewEnabled()) {
|
||||
@@ -461,7 +463,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
}
|
||||
|
||||
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
|
||||
if (isThreading && !EventHandler::isThreaded(m_event.get())) {
|
||||
if (isThreading && !EventHandler::isThreaded(event)) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
}
|
||||
|
||||
@@ -470,11 +472,12 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
|
||||
void MessageContentModel::updateReplyModel()
|
||||
{
|
||||
if (m_event == nullptr || m_isReply) {
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr || m_isReply) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EventHandler::hasReply(m_event.get()) || (EventHandler::isThreaded(m_event.get()) && NeoChatConfig::self()->threads())) {
|
||||
if (!EventHandler::hasReply(event) || (EventHandler::isThreaded(event) && NeoChatConfig::self()->threads())) {
|
||||
if (m_replyModel) {
|
||||
delete m_replyModel;
|
||||
}
|
||||
@@ -485,12 +488,7 @@ void MessageContentModel::updateReplyModel()
|
||||
return;
|
||||
}
|
||||
|
||||
const auto replyEvent = m_room->findInTimeline(EventHandler::replyId(m_event.get()));
|
||||
if (replyEvent == m_room->historyEdge()) {
|
||||
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(m_event.get()), true, false, this);
|
||||
} else {
|
||||
m_replyModel = new MessageContentModel(m_room, replyEvent->get(), true, false, this);
|
||||
}
|
||||
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event), true, false, this);
|
||||
|
||||
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
|
||||
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
|
||||
@@ -499,28 +497,46 @@ void MessageContentModel::updateReplyModel()
|
||||
|
||||
QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentType::Type type)
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MessageComponentType::Text: {
|
||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||
auto body = EventHandler::rawMessageBody(*event);
|
||||
return TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
return TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
}
|
||||
case MessageComponentType::File: {
|
||||
QList<MessageComponent> components;
|
||||
components += MessageComponent{MessageComponentType::File, QString(), {}};
|
||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
|
||||
if (m_emptyItinerary) {
|
||||
if (!m_isReply) {
|
||||
auto fileTransferInfo = m_room->cachedFileTransferInfo(m_event.get());
|
||||
auto fileTransferInfo = m_room->cachedFileTransferInfo(event);
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
Q_ASSERT(event->content() != nullptr && event->content()->fileInfo() != nullptr);
|
||||
const QMimeType mimeType = event->content()->fileInfo()->mimeType;
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->has<EventContent::FileContent>());
|
||||
const QMimeType mimeType = roomMessageEvent->get<EventContent::FileContent>()->mimeType;
|
||||
#else
|
||||
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->hasFileContent());
|
||||
const QMimeType mimeType = roomMessageEvent->content()->fileInfo()->mimeType;
|
||||
#endif
|
||||
if (mimeType.name() == QStringLiteral("text/plain") || mimeType.parentMimeTypes().contains(QStringLiteral("text/plain"))) {
|
||||
QString originalName = event->content()->fileInfo()->originalName;
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString originalName = roomMessageEvent->get<EventContent::FileContent>()->originalName;
|
||||
#else
|
||||
QString originalName = roomMessageEvent->content()->fileInfo()->originalName;
|
||||
#endif
|
||||
if (originalName.isEmpty()) {
|
||||
originalName = event->plainBody();
|
||||
originalName = roomMessageEvent->plainBody();
|
||||
}
|
||||
KSyntaxHighlighting::Repository repository;
|
||||
KSyntaxHighlighting::Definition definitionForFile = repository.definitionForFileName(originalName);
|
||||
@@ -549,19 +565,27 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
||||
} else {
|
||||
updateItineraryModel();
|
||||
}
|
||||
auto body = EventHandler::rawMessageBody(*event);
|
||||
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
|
||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
components += TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
return components;
|
||||
}
|
||||
case MessageComponentType::Image:
|
||||
case MessageComponentType::Audio:
|
||||
case MessageComponentType::Video: {
|
||||
if (!m_event->is<StickerEvent>()) {
|
||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||
if (!event->is<StickerEvent>()) {
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
QList<MessageComponent> components;
|
||||
components += MessageComponent{type, QString(), {}};
|
||||
auto body = EventHandler::rawMessageBody(*event);
|
||||
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
|
||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
components += TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
return components;
|
||||
}
|
||||
}
|
||||
@@ -579,7 +603,7 @@ MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
|
||||
if (linkPreviewer->loaded()) {
|
||||
return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_ls, link}}};
|
||||
} else {
|
||||
connect(linkPreviewer, &LinkPreviewer::loadedChanged, [this, link]() {
|
||||
connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() {
|
||||
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
|
||||
for (auto &component : m_components) {
|
||||
@@ -620,6 +644,11 @@ QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageCompon
|
||||
|
||||
void MessageContentModel::closeLinkPreview(int row)
|
||||
{
|
||||
if (row < 0 || row > m_components.size()) {
|
||||
qWarning() << "closeLinkPreview() called with row" << row << "which does not exist. m_components.size() =" << m_components.size();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_components[row].type == MessageComponentType::LinkPreview || m_components[row].type == MessageComponentType::LinkPreviewLoad) {
|
||||
beginResetModel();
|
||||
m_removedLinkPreviews += m_components[row].attributes["link"_ls].toUrl();
|
||||
@@ -631,13 +660,18 @@ void MessageContentModel::closeLinkPreview(int row)
|
||||
|
||||
void MessageContentModel::updateItineraryModel()
|
||||
{
|
||||
if (m_room == nullptr || m_event == nullptr) {
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (m_room == nullptr || event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||
if (event->hasFileContent()) {
|
||||
auto filePath = m_room->cachedFileTransferInfo(m_event.get()).localPath;
|
||||
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (roomMessageEvent->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (roomMessageEvent->hasFileContent()) {
|
||||
#endif
|
||||
auto filePath = m_room->cachedFileTransferInfo(event).localPath;
|
||||
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
|
||||
delete m_itineraryModel;
|
||||
m_itineraryModel = nullptr;
|
||||
|
||||
@@ -11,24 +11,9 @@
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "itinerarymodel.h"
|
||||
#include "messagecomponent.h"
|
||||
#include "neochatroommember.h"
|
||||
|
||||
struct MessageComponent {
|
||||
MessageComponentType::Type type = MessageComponentType::Other;
|
||||
QString content;
|
||||
QVariantMap attributes;
|
||||
|
||||
int operator==(const MessageComponent &right) const
|
||||
{
|
||||
return type == right.type && content == right.content && attributes == right.attributes;
|
||||
}
|
||||
|
||||
bool isEmpty() const
|
||||
{
|
||||
return type == MessageComponentType::Other;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class MessageContentModel
|
||||
*
|
||||
@@ -75,11 +60,10 @@ public:
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit MessageContentModel(NeoChatRoom *room,
|
||||
const Quotient::RoomEvent *event,
|
||||
const QString &eventId,
|
||||
bool isReply = false,
|
||||
bool isPending = false,
|
||||
MessageContentModel *parent = nullptr);
|
||||
MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply = false, bool isPending = false, MessageContentModel *parent = nullptr);
|
||||
|
||||
bool showAuthor() const;
|
||||
void setShowAuthor(bool showAuthor);
|
||||
@@ -114,6 +98,7 @@ public:
|
||||
|
||||
Q_SIGNALS:
|
||||
void showAuthorChanged();
|
||||
void eventUnavailable();
|
||||
void eventUpdated();
|
||||
|
||||
private:
|
||||
@@ -121,7 +106,6 @@ private:
|
||||
QString m_eventId;
|
||||
QString m_eventSenderId;
|
||||
std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr;
|
||||
Quotient::RoomEventPtr m_event;
|
||||
|
||||
bool m_isPending;
|
||||
bool m_showAuthor = true;
|
||||
@@ -129,8 +113,8 @@ private:
|
||||
bool m_notFound = false;
|
||||
|
||||
void initializeModel();
|
||||
void intiializeEvent(const QString &eventId);
|
||||
void intiializeEvent(const Quotient::RoomEvent *event);
|
||||
void initializeEvent();
|
||||
void getEvent();
|
||||
|
||||
QList<MessageComponent> m_components;
|
||||
void resetModel();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "neochatconfig.h"
|
||||
|
||||
#include <Quotient/csapi/rooms.h>
|
||||
#include <Quotient/events/eventcontent.h>
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
@@ -17,6 +18,7 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include <KFormat>
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
@@ -411,7 +413,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return DelegateType::ReadMarker;
|
||||
case TimeRole: {
|
||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
|
||||
const KFormat format;
|
||||
static const KFormat format;
|
||||
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
||||
}
|
||||
case SpecialMarksRole:
|
||||
@@ -433,11 +435,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (evt.isRedacted()) {
|
||||
auto reason = evt.redactedBecause()->reason();
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
|
||||
: i18n("<i>[This message was deleted: %1]</i>", evt.redactedBecause()->reason());
|
||||
}
|
||||
return EventHandler::richBody(m_currentRoom, &evt);
|
||||
}
|
||||
|
||||
@@ -455,7 +452,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == GenericDisplayRole) {
|
||||
return EventHandler::genericBody(&evt);
|
||||
return EventHandler::genericBody(m_currentRoom, &evt);
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
@@ -508,7 +505,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (role == ProgressInfoRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (e->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (e->hasFileContent()) {
|
||||
#endif
|
||||
return QVariant::fromValue(m_currentRoom->cachedFileTransferInfo(&evt));
|
||||
}
|
||||
}
|
||||
@@ -644,7 +645,7 @@ void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
|
||||
|
||||
if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) {
|
||||
if (!event->isStateEvent() || event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, event));
|
||||
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, eventId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <KFormat>
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
@@ -118,7 +117,6 @@ private:
|
||||
int rowBelowInserted = -1;
|
||||
bool resetting = false;
|
||||
bool movingEvent = false;
|
||||
KFormat m_format;
|
||||
|
||||
std::map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
|
||||
std::map<QString, std::unique_ptr<MessageContentModel>> m_contentModels;
|
||||
|
||||
@@ -133,14 +133,11 @@ bool MessageFilterModel::showAuthor(QModelIndex index) const
|
||||
|
||||
QString MessageFilterModel::aggregateEventToString(int sourceRow) const
|
||||
{
|
||||
QStringList parts;
|
||||
QVariantList uniqueAuthors;
|
||||
QString aggregateString;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||
aggregateString += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||
aggregateString += ", "_ls;
|
||||
QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||
uniqueAuthors.append(nextAuthor);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) != DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
@@ -148,46 +145,12 @@ QString MessageFilterModel::aggregateEventToString(int sourceRow) const
|
||||
break;
|
||||
}
|
||||
}
|
||||
parts.sort(); // Sort them so that all identical events can be collected.
|
||||
if (!parts.isEmpty()) {
|
||||
QStringList chunks;
|
||||
while (!parts.isEmpty()) {
|
||||
chunks += QString();
|
||||
int count = 1;
|
||||
auto part = parts.takeFirst();
|
||||
while (!parts.isEmpty() && parts.first() == part) {
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
chunks.last() += i18ncp("%1: What's being done; %2: How often it is done.", " %1", " %1 %2 times", part, count);
|
||||
}
|
||||
chunks.removeDuplicates();
|
||||
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped());
|
||||
|
||||
QString chunksText;
|
||||
chunksText += chunks.takeFirst();
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
chunksText += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||
chunksText += chunks.takeFirst();
|
||||
}
|
||||
chunksText +=
|
||||
uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
chunksText += chunks.takeFirst();
|
||||
}
|
||||
return i18nc(
|
||||
"userText (%1) is either a Matrix username if a single user sent all the states or n users if they were sent by multiple users."
|
||||
"chunksText (%2) is a list of comma separated actions for each of the state events in the group.",
|
||||
"<style>a {text-decoration: none;}</style>%1 %2",
|
||||
userText,
|
||||
chunksText);
|
||||
} else {
|
||||
return {};
|
||||
aggregateString = aggregateString.trimmed();
|
||||
if (aggregateString.endsWith(u',')) {
|
||||
aggregateString.removeLast();
|
||||
}
|
||||
return aggregateString;
|
||||
}
|
||||
|
||||
QVariantList MessageFilterModel::stateEventsList(int sourceRow) const
|
||||
|
||||
@@ -272,7 +272,7 @@ void PermissionsModel::setPowerLevel(const QString &permission, const int &newPo
|
||||
powerLevelContent[QLatin1String("events")] = eventPowerLevels;
|
||||
}
|
||||
|
||||
m_room->setState<Quotient::RoomPowerLevelsEvent>(powerLevelContent);
|
||||
m_room->setState<Quotient::RoomPowerLevelsEvent>(Quotient::fromJson<Quotient::PowerLevelsEventContent>(powerLevelContent));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -311,8 +311,12 @@ void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
|
||||
pushConditions.append(keywordCondition);
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto job = m_connection->callApi<Quotient::SetPushRuleJob>(PushRuleKind::kindString(kind),
|
||||
#else
|
||||
auto job = m_connection->callApi<Quotient::SetPushRuleJob>(QLatin1String("global"),
|
||||
PushRuleKind::kindString(kind),
|
||||
#endif
|
||||
keyword,
|
||||
actions,
|
||||
QString(),
|
||||
@@ -337,7 +341,11 @@ void PushRuleModel::removeKeyword(const QString &keyword)
|
||||
}
|
||||
|
||||
auto kind = PushRuleKind::kindString(m_rules[index].kind);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto job = m_connection->callApi<Quotient::DeletePushRuleJob>(kind, m_rules[index].id);
|
||||
#else
|
||||
auto job = m_connection->callApi<Quotient::DeletePushRuleJob>(QStringLiteral("global"), kind, m_rules[index].id);
|
||||
#endif
|
||||
connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() {
|
||||
qWarning() << QLatin1String("Unable to remove push rule for keyword %1: ").arg(m_rules[index].id) << job->errorString();
|
||||
});
|
||||
@@ -345,10 +353,18 @@ void PushRuleModel::removeKeyword(const QString &keyword)
|
||||
|
||||
void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled)
|
||||
{
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto job = m_connection->callApi<Quotient::IsPushRuleEnabledJob>(kind, ruleId);
|
||||
#else
|
||||
auto job = m_connection->callApi<Quotient::IsPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId);
|
||||
#endif
|
||||
connect(job, &Quotient::BaseJob::success, this, [job, kind, ruleId, enabled, this]() {
|
||||
if (job->enabled() != enabled) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
m_connection->callApi<Quotient::SetPushRuleEnabledJob>(kind, ruleId, enabled);
|
||||
#else
|
||||
m_connection->callApi<Quotient::SetPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId, enabled);
|
||||
#endif
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -362,7 +378,11 @@ void PushRuleModel::setNotificationRuleActions(const QString &kind, const QStrin
|
||||
actions = actionToVariant(action);
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
m_connection->callApi<Quotient::SetPushRuleActionsJob>(kind, ruleId, actions);
|
||||
#else
|
||||
m_connection->callApi<Quotient::SetPushRuleActionsJob>(QStringLiteral("global"), kind, ruleId, actions);
|
||||
#endif
|
||||
}
|
||||
|
||||
PushRuleAction::Action PushRuleModel::variantToAction(const QList<QVariant> &actions, bool enabled)
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "enums/neochatroomtype.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
namespace Quotient
|
||||
|
||||
@@ -95,7 +95,6 @@ void RoomTreeModel::newRoom(Room *r)
|
||||
parentItem->insertChild(std::make_unique<RoomTreeItem>(room, parentItem));
|
||||
connectRoomSignals(room);
|
||||
endInsertRows();
|
||||
qWarning() << "adding room" << type << "new count" << parentItem->childCount();
|
||||
}
|
||||
|
||||
void RoomTreeModel::leftRoom(Room *r)
|
||||
|
||||
@@ -115,11 +115,11 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
return EventHandler::threadRoot(&event);
|
||||
case ContentModelRole: {
|
||||
if (!event.isStateEvent()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, &event));
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, event.id()));
|
||||
}
|
||||
if (event.isStateEvent()) {
|
||||
if (event.matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, &event));
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, event.id()));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -38,6 +38,9 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
|
||||
*/
|
||||
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
// This is a lazy hack to make this model compatible with SearchPage. TODO: rename the property entirely
|
||||
Q_PROPERTY(QString searchText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
public:
|
||||
explicit SortFilterRoomListModel(RoomListModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ void ThreadModel::fetchMore(const QModelIndex &parent)
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
auto newEvents = m_currentJob->chunk();
|
||||
for (auto &event : newEvents) {
|
||||
m_contentModels.push_back(new MessageContentModel(room, event.get()));
|
||||
m_contentModels.push_back(new MessageContentModel(room, event->id()));
|
||||
}
|
||||
|
||||
addModels();
|
||||
@@ -103,7 +103,7 @@ void ThreadModel::fetchMore(const QModelIndex &parent)
|
||||
void ThreadModel::addNewEvent(const Quotient::RoomEvent *event)
|
||||
{
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
m_contentModels.push_front(new MessageContentModel(room, event));
|
||||
m_contentModels.push_front(new MessageContentModel(room, event->id()));
|
||||
}
|
||||
|
||||
void ThreadModel::addModels()
|
||||
|
||||
@@ -94,7 +94,7 @@ int TimelineBeginningModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
if (m_room == nullptr) {
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
return m_room->successorId().isEmpty() ? 0 : 1;
|
||||
}
|
||||
@@ -159,7 +159,7 @@ int TimelineEndModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
if (m_room == nullptr) {
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
return m_room->predecessorId().isEmpty() ? 1 : (m_room->allHistoryLoaded() ? 2 : 1);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
bool UserFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
Q_UNUSED(sourceParent);
|
||||
if (m_filterText.length() < 1) {
|
||||
if (!m_allowEmpty && m_filterText.length() < 1) {
|
||||
return false;
|
||||
}
|
||||
return sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
@@ -27,4 +27,15 @@ void UserFilterModel::setFilterText(const QString &filterText)
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool UserFilterModel::allowEmpty() const
|
||||
{
|
||||
return m_allowEmpty;
|
||||
}
|
||||
|
||||
void UserFilterModel::setAllowEmpty(bool allowEmpty)
|
||||
{
|
||||
m_allowEmpty = allowEmpty;
|
||||
Q_EMIT allowEmptyChanged();
|
||||
}
|
||||
|
||||
#include "moc_userfiltermodel.cpp"
|
||||
|
||||
@@ -24,6 +24,7 @@ class UserFilterModel : public QSortFilterProxyModel
|
||||
* The text is either a desired display name or matrix id.
|
||||
*/
|
||||
Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
Q_PROPERTY(bool allowEmpty READ allowEmpty WRITE setAllowEmpty NOTIFY allowEmptyChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -36,9 +37,14 @@ public:
|
||||
QString filterText() const;
|
||||
void setFilterText(const QString &filterText);
|
||||
|
||||
bool allowEmpty() const;
|
||||
void setAllowEmpty(bool allowEmpty);
|
||||
|
||||
Q_SIGNALS:
|
||||
void filterTextChanged();
|
||||
void allowEmptyChanged();
|
||||
|
||||
private:
|
||||
QString m_filterText;
|
||||
bool m_allowEmpty = false;
|
||||
};
|
||||
|
||||
@@ -87,7 +87,11 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
||||
return memberId;
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return m_currentRoom->member(memberId).avatarUrl();
|
||||
#else
|
||||
return m_currentRoom->memberAvatar(memberId).url();
|
||||
#endif
|
||||
}
|
||||
if (role == ObjectRole) {
|
||||
return QVariant::fromValue(memberId);
|
||||
|
||||
@@ -263,6 +263,8 @@ Name[ar]=شارك
|
||||
Name[ca]=Compartició
|
||||
Name[ca@valencia]=Compartició
|
||||
Name[cs]=Sdílet
|
||||
Name[de]=Teilen
|
||||
Name[el]=Κοινοποίηση
|
||||
Name[en_GB]=Share
|
||||
Name[eo]=Kundividi
|
||||
Name[es]=Compartir
|
||||
@@ -280,6 +282,7 @@ Name[nl]=Gedeelde
|
||||
Name[nn]=Del
|
||||
Name[pl]=Udostępnij
|
||||
Name[pt_BR]=Compartilhar
|
||||
Name[ru]=Публикация
|
||||
Name[sl]=Deli
|
||||
Name[sv]=Dela
|
||||
Name[ta]=பகிர்
|
||||
@@ -291,6 +294,8 @@ Comment=The result of sharing a piece of content
|
||||
Comment[ar]=نتيجة مشاركة محتوى
|
||||
Comment[ca]=El resultat de compartir una peça de contingut
|
||||
Comment[ca@valencia]=El resultat de compartir una peça de contingut
|
||||
Comment[de]=Das Ergebnis nach dem Teilen eines Teils des Inhalts
|
||||
Comment[el]=Το αποτέλεσμα του διαμοιρασμού ενός περιεχομένου
|
||||
Comment[en_GB]=The result of sharing a piece of content
|
||||
Comment[eo]=La rezulto el kundividado de enhavero
|
||||
Comment[es]=El resultado de compartir una parte de contenido
|
||||
@@ -308,6 +313,7 @@ Comment[nl]=Het resultaat van het delen van een stukje inhoud
|
||||
Comment[nn]=Resultatet av deling av innhald
|
||||
Comment[pl]=Wynik udostępniania kawałka treści
|
||||
Comment[pt_BR]=O resultado de compartilhar um conteúdo
|
||||
Comment[ru]=Результат публикации данных
|
||||
Comment[sl]=Rezultat deljenega kosa vsebine
|
||||
Comment[sv]=Resultatet av att dela innehåll
|
||||
Comment[ta]=எதையோ பகிர்ந்ததன் விளைவு
|
||||
|
||||
@@ -6,13 +6,10 @@
|
||||
#include <QImageReader>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "controller.h"
|
||||
#include "jobs/neochatchangepasswordjob.h"
|
||||
#include "jobs/neochatdeactivateaccountjob.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "roommanager.h"
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
@@ -68,10 +65,6 @@ void NeoChatConnection::connectSignals()
|
||||
});
|
||||
connect(this, &NeoChatConnection::syncDone, this, [this] {
|
||||
setIsOnline(true);
|
||||
|
||||
connect(this, &NeoChatConnection::syncDone, this, [this]() {
|
||||
NotificationsManager::instance().handleNotifications(this);
|
||||
});
|
||||
});
|
||||
connect(this, &NeoChatConnection::networkError, this, [this]() {
|
||||
setIsOnline(false);
|
||||
@@ -81,9 +74,9 @@ void NeoChatConnection::connectSignals()
|
||||
Q_EMIT userConsentRequired(job->errorUrl());
|
||||
}
|
||||
});
|
||||
connect(this, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
|
||||
connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
|
||||
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
|
||||
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
|
||||
Q_EMIT showMessage(MessageType::Warning, i18n("File too large to download.<br />Contact your matrix server administrator for support."));
|
||||
}
|
||||
});
|
||||
connect(this, &NeoChatConnection::directChatsListChanged, this, [this](DirectChatsMap additions, DirectChatsMap removals) {
|
||||
@@ -116,6 +109,10 @@ void NeoChatConnection::connectSignals()
|
||||
Q_EMIT homeHaveHighlightNotificationsChanged();
|
||||
});
|
||||
});
|
||||
connect(this, &NeoChatConnection::invitedRoom, this, [this](Quotient::Room *room) {
|
||||
auto r = dynamic_cast<NeoChatRoom *>(room);
|
||||
connect(r, &NeoChatRoom::showInviteNotification, this, &NeoChatConnection::showInviteNotification);
|
||||
});
|
||||
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
|
||||
Q_UNUSED(room)
|
||||
if (prev && prev->isDirectChat()) {
|
||||
@@ -344,17 +341,9 @@ void NeoChatConnection::createRoom(const QString &name, const QString &topic, co
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [job] {
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Room creation failed: %1", job->errorString()), {});
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT errorOccured(i18n("Room creation failed: %1", job->errorString()));
|
||||
});
|
||||
connect(
|
||||
this,
|
||||
&Connection::newRoom,
|
||||
this,
|
||||
[](Room *room) {
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
|
||||
void NeoChatConnection::createSpace(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
|
||||
@@ -381,17 +370,9 @@ void NeoChatConnection::createSpace(const QString &name, const QString &topic, c
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [job] {
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Space creation failed: %1", job->errorString()), {});
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT errorOccured(i18n("Space creation failed: %1", job->errorString()));
|
||||
});
|
||||
connect(
|
||||
this,
|
||||
&Connection::newRoom,
|
||||
this,
|
||||
[](Room *room) {
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
|
||||
bool NeoChatConnection::directChatExists(Quotient::User *user)
|
||||
@@ -399,29 +380,6 @@ bool NeoChatConnection::directChatExists(Quotient::User *user)
|
||||
return directChats().contains(user);
|
||||
}
|
||||
|
||||
void NeoChatConnection::openOrCreateDirectChat(const QString &userId)
|
||||
{
|
||||
if (auto user = this->user(userId)) {
|
||||
openOrCreateDirectChat(user);
|
||||
} else {
|
||||
qWarning() << "openOrCreateDirectChat: Couldn't get user object for ID " << userId << ", unable to open/request direct chat.";
|
||||
}
|
||||
}
|
||||
|
||||
void NeoChatConnection::openOrCreateDirectChat(User *user)
|
||||
{
|
||||
const auto existing = directChats();
|
||||
|
||||
if (existing.contains(user)) {
|
||||
const auto room = this->room(existing.value(user));
|
||||
if (room) {
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
return;
|
||||
}
|
||||
}
|
||||
requestDirectChat(user->id());
|
||||
}
|
||||
|
||||
qsizetype NeoChatConnection::directChatNotifications() const
|
||||
{
|
||||
qsizetype notifications = 0;
|
||||
@@ -518,6 +476,7 @@ QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
|
||||
qWarning() << "There's no gateway, not setting up push notifications.";
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(endpoint)
|
||||
co_return;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <Quotient/keyimport.h>
|
||||
#endif
|
||||
|
||||
#include "enums/messagetype.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "models/threepidmodel.h"
|
||||
|
||||
@@ -154,20 +155,6 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE bool directChatExists(Quotient::User *user);
|
||||
|
||||
/**
|
||||
* @brief Join a direct chat with the given user ID.
|
||||
*
|
||||
* If a direct chat with the user doesn't exist one is created and then joined.
|
||||
*/
|
||||
Q_INVOKABLE void openOrCreateDirectChat(const QString &userId);
|
||||
|
||||
/**
|
||||
* @brief Join a direct chat with the given user object.
|
||||
*
|
||||
* If a direct chat with the user doesn't exist one is created and then joined.
|
||||
*/
|
||||
Q_INVOKABLE void openOrCreateDirectChat(Quotient::User *user);
|
||||
|
||||
/**
|
||||
* @brief Get the account data with \param type as a formatted JSON string.
|
||||
*/
|
||||
@@ -212,6 +199,21 @@ Q_SIGNALS:
|
||||
void badgeNotificationCountChanged(NeoChatConnection *connection, int count);
|
||||
void canCheckMutualRoomsChanged();
|
||||
|
||||
/**
|
||||
* @brief Request a message be shown to the user of the given type.
|
||||
*/
|
||||
void showMessage(MessageType::Type messageType, const QString &message);
|
||||
|
||||
/**
|
||||
* @brief Request a error message be shown to the user.
|
||||
*/
|
||||
void errorOccured(const QString &error);
|
||||
|
||||
/**
|
||||
* @brief Request a notification be shown for an invite to this room.
|
||||
*/
|
||||
void showInviteNotification(NeoChatRoom *room);
|
||||
|
||||
private:
|
||||
bool m_isOnline = true;
|
||||
void setIsOnline(bool isOnline);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QMimeDatabase>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include <Quotient/events/eventcontent.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <qcoro/qcorosignal.h>
|
||||
@@ -36,19 +37,15 @@
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "clipboard.h"
|
||||
#include "controller.h"
|
||||
#include "eventhandler.h"
|
||||
#include "events/joinrulesevent.h"
|
||||
#include "events/pollevent.h"
|
||||
#include "filetransferpseudojob.h"
|
||||
#include "jobs/neochatgetcommonroomsjob.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "roomlastmessageprovider.h"
|
||||
#include "spacehierarchycache.h"
|
||||
#include "texthandler.h"
|
||||
#include "urlhelper.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <KIO/Job>
|
||||
@@ -77,11 +74,16 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
const auto m_event = evtIt->viewAs<RoomEvent>();
|
||||
QString mxcUrl;
|
||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event->has<EventContent::FileContentBase>()) {
|
||||
mxcUrl = event->get<EventContent::FileContentBase>()->url().toString();
|
||||
#else
|
||||
if (event->hasFileContent()) {
|
||||
mxcUrl = event->content()->fileInfo()->url().toString();
|
||||
#endif
|
||||
}
|
||||
} else if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
|
||||
mxcUrl = event->image().fileInfo()->url().toString();
|
||||
mxcUrl = event->image().url().toString();
|
||||
}
|
||||
if (mxcUrl.isEmpty()) {
|
||||
return;
|
||||
@@ -127,41 +129,8 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
updatePushNotificationState(QStringLiteral("m.push_rules"));
|
||||
|
||||
Q_EMIT canEncryptRoomChanged();
|
||||
if (this->joinState() != JoinState::Invite) {
|
||||
return;
|
||||
}
|
||||
auto roomMemberEvent = currentState().get<RoomMemberEvent>(localMember().id());
|
||||
|
||||
auto showNotification = [this, roomMemberEvent] {
|
||||
QImage avatar_image;
|
||||
if (roomMemberEvent && !member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
|
||||
avatar_image = memberAvatar(roomMemberEvent->senderId()).get(this->connection(), 128, [] {});
|
||||
} else {
|
||||
qWarning() << "using this room's avatar";
|
||||
avatar_image = avatar(128);
|
||||
}
|
||||
|
||||
NotificationsManager::instance().postInviteNotification(this,
|
||||
displayName(),
|
||||
member(roomMemberEvent->senderId()).htmlSafeDisplayName(),
|
||||
avatar_image);
|
||||
};
|
||||
|
||||
if (NeoChatConfig::rejectUnknownInvites()) {
|
||||
auto job = this->connection()->callApi<NeochatGetCommonRoomsJob>(roomMemberEvent->senderId());
|
||||
connect(job, &BaseJob::result, this, [this, job, showNotification] {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
if (replyData.contains(QStringLiteral("joined"))) {
|
||||
const bool inAnyOfOurRooms = !replyData[QStringLiteral("joined")].toArray().isEmpty();
|
||||
if (inAnyOfOurRooms) {
|
||||
showNotification();
|
||||
} else {
|
||||
leaveRoom();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showNotification();
|
||||
if (this->joinState() == JoinState::Invite) {
|
||||
Q_EMIT showInviteNotification(this);
|
||||
}
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
@@ -252,7 +221,11 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
|
||||
auto mime = QMimeDatabase().mimeTypeForUrl(url);
|
||||
url.setScheme("file"_ls);
|
||||
QFileInfo fileInfo(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
EventContent::FileContentBase *content;
|
||||
#else
|
||||
EventContent::TypedBase *content;
|
||||
#endif
|
||||
if (mime.name().startsWith("image/"_ls)) {
|
||||
QImage image(url.toLocalFile());
|
||||
content = new EventContent::ImageContent(url, fileInfo.size(), mime, image.size(), fileInfo.fileName());
|
||||
@@ -267,7 +240,11 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
|
||||
} else {
|
||||
content = new EventContent::FileContent(url, fileInfo.size(), mime, fileInfo.fileName());
|
||||
}
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, std::unique_ptr<EventContent::FileContentBase>(content));
|
||||
#else
|
||||
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, content);
|
||||
#endif
|
||||
setHasFileUploading(true);
|
||||
connect(this, &Room::fileTransferCompleted, [this, txnId](const QString &id, FileSourceInfo) {
|
||||
if (id == txnId) {
|
||||
@@ -399,8 +376,13 @@ bool NeoChatRoom::lastEventIsSpoiler() const
|
||||
{
|
||||
if (auto event = lastEvent()) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (e->has<EventContent::TextContent>() && e->content() && e->mimeType().name() == "text/html"_ls) {
|
||||
auto htmlBody = e->get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
if (e->hasTextContent() && e->content() && e->mimeType().name() == "text/html"_ls) {
|
||||
auto htmlBody = static_cast<const Quotient::EventContent::TextContent *>(e->content())->body;
|
||||
#endif
|
||||
return htmlBody.contains("data-mx-spoiler"_ls);
|
||||
}
|
||||
}
|
||||
@@ -944,11 +926,6 @@ QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QStri
|
||||
}
|
||||
}
|
||||
|
||||
void NeoChatRoom::clearInvitationNotification()
|
||||
{
|
||||
NotificationsManager::instance().clearInvitationNotification(id());
|
||||
}
|
||||
|
||||
bool NeoChatRoom::hasParent() const
|
||||
{
|
||||
return currentState().eventsOfType("m.space.parent"_ls).size() > 0;
|
||||
@@ -1240,7 +1217,11 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
for (const auto &i : roomRuleArray) {
|
||||
QJsonObject roomRule = i.toObject();
|
||||
if (roomRule["rule_id"_ls] == id()) {
|
||||
connection()->callApi<DeletePushRuleJob>("global"_ls, "room"_ls, id());
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection()->callApi<DeletePushRuleJob>("room"_ls, id());
|
||||
#else
|
||||
connection()->callApi<DeletePushRuleJob>(QLatin1String("global"), "room"_ls, id());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1251,7 +1232,11 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
for (const auto &i : overrideRuleArray) {
|
||||
QJsonObject overrideRule = i.toObject();
|
||||
if (overrideRule["rule_id"_ls] == id()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection()->callApi<DeletePushRuleJob>("override"_ls, id());
|
||||
#else
|
||||
connection()->callApi<DeletePushRuleJob>("global"_ls, "override"_ls, id());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1287,9 +1272,17 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions = {pushCondition};
|
||||
|
||||
// Add new override rule and make sure it's enabled
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto job = connection()->callApi<SetPushRuleJob>("override"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#else
|
||||
auto job = connection()->callApi<SetPushRuleJob>("global"_ls, "override"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#endif
|
||||
connect(job, &BaseJob::success, this, [this]() {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("override"_ls, id(), true);
|
||||
#else
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("global"_ls, "override"_ls, id(), true);
|
||||
#endif
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1313,9 +1306,17 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
// No conditions for a room rule
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#else
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#endif
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("room"_ls, id(), true);
|
||||
#else
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
#endif
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1344,9 +1345,17 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
// Add new room rule and make sure enabled
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#else
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#endif
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("room"_ls, id(), true);
|
||||
#else
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
#endif
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1415,9 +1424,9 @@ void NeoChatRoom::updatePushNotificationState(QString type)
|
||||
void NeoChatRoom::reportEvent(const QString &eventId, const QString &reason)
|
||||
{
|
||||
auto job = connection()->callApi<ReportContentJob>(id(), eventId, -50, reason);
|
||||
connect(job, &BaseJob::finished, this, [job]() {
|
||||
connect(job, &BaseJob::finished, this, [this, job]() {
|
||||
if (job->error() == BaseJob::Success) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18n("Report sent successfully."));
|
||||
Q_EMIT showMessage(MessageType::Positive, i18n("Report sent successfully."));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1437,7 +1446,11 @@ void NeoChatRoom::openEventMediaExternally(const QString &eventId)
|
||||
const auto evtIt = findInTimeline(eventId);
|
||||
if (evtIt != messageEvents().rend() && is<RoomMessageEvent>(**evtIt)) {
|
||||
const auto event = evtIt->viewAs<RoomMessageEvent>();
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (event->hasFileContent()) {
|
||||
#endif
|
||||
const auto transferInfo = cachedFileTransferInfo(event);
|
||||
if (transferInfo.completed()) {
|
||||
UrlHelper helper;
|
||||
@@ -1470,7 +1483,11 @@ void NeoChatRoom::copyEventMedia(const QString &eventId)
|
||||
const auto evtIt = findInTimeline(eventId);
|
||||
if (evtIt != messageEvents().rend() && is<RoomMessageEvent>(**evtIt)) {
|
||||
const auto event = evtIt->viewAs<RoomMessageEvent>();
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (event->hasFileContent()) {
|
||||
#endif
|
||||
const auto transferInfo = fileTransferInfo(eventId);
|
||||
if (transferInfo.completed()) {
|
||||
Clipboard clipboard;
|
||||
@@ -1503,13 +1520,20 @@ FileTransferInfo NeoChatRoom::cachedFileTransferInfo(const Quotient::RoomEvent *
|
||||
QString mxcUrl;
|
||||
int total = 0;
|
||||
if (auto evt = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (evt->has<EventContent::FileContent>()) {
|
||||
const auto fileContent = evt->get<EventContent::FileContent>();
|
||||
#else
|
||||
if (evt->hasFileContent()) {
|
||||
mxcUrl = evt->content()->fileInfo()->url().toString();
|
||||
total = evt->content()->fileInfo()->payloadSize;
|
||||
const auto fileContent = evt->content()->fileInfo();
|
||||
#endif
|
||||
|
||||
mxcUrl = fileContent->url().toString();
|
||||
total = fileContent->payloadSize;
|
||||
}
|
||||
} else if (auto evt = eventCast<const Quotient::StickerEvent>(event)) {
|
||||
mxcUrl = evt->image().fileInfo()->url().toString();
|
||||
total = evt->image().fileInfo()->payloadSize;
|
||||
mxcUrl = evt->image().url().toString();
|
||||
total = evt->image().payloadSize;
|
||||
}
|
||||
|
||||
FileTransferInfo transferInfo = fileTransferInfo(event->id());
|
||||
@@ -1727,9 +1751,13 @@ int NeoChatRoom::maxRoomVersion() const
|
||||
return maxVersion;
|
||||
}
|
||||
|
||||
Quotient::RoomMember NeoChatRoom::directChatRemoteMember() const
|
||||
NeochatRoomMember *NeoChatRoom::directChatRemoteMember()
|
||||
{
|
||||
return directChatMembers()[0];
|
||||
if (directChatMembers().size() == 0) {
|
||||
qWarning() << "No other member available in this room";
|
||||
return {};
|
||||
}
|
||||
return new NeochatRoomMember(this, directChatMembers()[0].id());
|
||||
}
|
||||
|
||||
void NeoChatRoom::sendLocation(float lat, float lon, const QString &description)
|
||||
@@ -1764,6 +1792,9 @@ QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType)
|
||||
void NeoChatRoom::downloadEventFromServer(const QString &eventId)
|
||||
{
|
||||
if (findInTimeline(eventId) != historyEdge()) {
|
||||
// For whatever reason the event has now appeared so the function that called
|
||||
// this need to whatever it wanted to do with the event.
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
return;
|
||||
}
|
||||
auto job = connection()->callApi<GetOneRoomEventJob>(id(), eventId);
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
#include <QCoroTask>
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
#include "enums/messagetype.h"
|
||||
#include "enums/pushrule.h"
|
||||
#include "neochatroommember.h"
|
||||
#include "pollhandler.h"
|
||||
|
||||
namespace Quotient
|
||||
@@ -74,7 +76,7 @@ class NeoChatRoom : public Quotient::Room
|
||||
/**
|
||||
* @brief Get a RoomMember object for the other person in a direct chat.
|
||||
*/
|
||||
Q_PROPERTY(Quotient::RoomMember directChatRemoteMember READ directChatRemoteMember CONSTANT)
|
||||
Q_PROPERTY(NeochatRoomMember *directChatRemoteMember READ directChatRemoteMember CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The Matrix IDs of this room's parents.
|
||||
@@ -324,7 +326,7 @@ public:
|
||||
|
||||
[[nodiscard]] QString avatarMediaId() const;
|
||||
|
||||
Quotient::RoomMember directChatRemoteMember() const;
|
||||
NeochatRoomMember *directChatRemoteMember();
|
||||
|
||||
/**
|
||||
* @brief Whether this room has one or more parent spaces set.
|
||||
@@ -416,8 +418,6 @@ public:
|
||||
|
||||
bool readOnly() const;
|
||||
|
||||
Q_INVOKABLE void clearInvitationNotification();
|
||||
|
||||
[[nodiscard]] QString joinRule() const;
|
||||
|
||||
/**
|
||||
@@ -657,6 +657,19 @@ Q_SIGNALS:
|
||||
void extraEventLoaded(const QString &eventId);
|
||||
void extraEventNotFound(const QString &eventId);
|
||||
|
||||
/**
|
||||
* @brief Request a message be shown to the user of the given type.
|
||||
*/
|
||||
void showMessage(MessageType::Type messageType, const QString &message);
|
||||
|
||||
/**
|
||||
* @brief Request a notification be shown for an invite to this room.
|
||||
*
|
||||
* @note This may later be blocked if there are any rules on where invites can
|
||||
* come from, but this is not NeoChatRoom's responsibility.
|
||||
*/
|
||||
void showInviteNotification(NeoChatRoom *room);
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* @brief Upload a file to the matrix server and post the file to the room.
|
||||
|
||||
@@ -72,7 +72,12 @@ QString NeochatRoomMember::displayName() const
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).displayName();
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.displayName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.displayName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::htmlSafeDisplayName() const
|
||||
@@ -81,7 +86,12 @@ QString NeochatRoomMember::htmlSafeDisplayName() const
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).htmlSafeDisplayName();
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.htmlSafeDisplayName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.htmlSafeDisplayName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::fullName() const
|
||||
@@ -90,7 +100,12 @@ QString NeochatRoomMember::fullName() const
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).fullName();
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.fullName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.fullName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::htmlSafeFullName() const
|
||||
@@ -99,7 +114,12 @@ QString NeochatRoomMember::htmlSafeFullName() const
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).htmlSafeFullName();
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.htmlSafeFullName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.htmlSafeFullName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::disambiguatedName() const
|
||||
@@ -108,7 +128,12 @@ QString NeochatRoomMember::disambiguatedName() const
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).disambiguatedName();
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.disambiguatedName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.disambiguatedName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::htmlSafeDisambiguatedName() const
|
||||
@@ -117,7 +142,12 @@ QString NeochatRoomMember::htmlSafeDisambiguatedName() const
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).htmlSafeDisambiguatedName();
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.htmlSafeDisambiguatedName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.htmlSafeDisambiguatedName();
|
||||
#endif
|
||||
}
|
||||
|
||||
int NeochatRoomMember::hue() const
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <KNotification>
|
||||
#include <KNotificationPermission>
|
||||
#include <KNotificationReplyAction>
|
||||
|
||||
#include <QPainter>
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/csapi/pushrules.h>
|
||||
#include <Quotient/events/roommemberevent.h>
|
||||
#include <Quotient/user.h>
|
||||
|
||||
#ifdef HAVE_KIO
|
||||
@@ -21,6 +23,8 @@
|
||||
#endif
|
||||
|
||||
#include "controller.h"
|
||||
#include "jobs/neochatgetcommonroomsjob.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
@@ -29,12 +33,6 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NotificationsManager &NotificationsManager::instance()
|
||||
{
|
||||
static NotificationsManager _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
NotificationsManager::NotificationsManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
@@ -42,6 +40,25 @@ NotificationsManager::NotificationsManager(QObject *parent)
|
||||
|
||||
void NotificationsManager::handleNotifications(QPointer<NeoChatConnection> connection)
|
||||
{
|
||||
if (KNotificationPermission::checkPermission() == Qt::PermissionStatus::Granted) {
|
||||
startNotificationJob(connection);
|
||||
} else if (!permissionAsked) {
|
||||
KNotificationPermission::requestPermission(this, [this, connection](Qt::PermissionStatus result) {
|
||||
if (result == Qt::PermissionStatus::Granted) {
|
||||
startNotificationJob(connection);
|
||||
} else {
|
||||
permissionAsked = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::startNotificationJob(QPointer<NeoChatConnection> connection)
|
||||
{
|
||||
if (connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_connActiveJob.contains(connection->user()->id())) {
|
||||
auto job = connection->callApi<GetNotificationsJob>();
|
||||
m_connActiveJob.append(connection->user()->id());
|
||||
@@ -131,7 +148,11 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
|
||||
|
||||
QImage avatar_image;
|
||||
if (!sender.avatarUrl().isEmpty()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
avatar_image = room->member(sender.id()).avatar(128, 128, {});
|
||||
#else
|
||||
avatar_image = room->memberAvatar(sender.id()).get(connection, 128, {});
|
||||
#endif
|
||||
} else {
|
||||
avatar_image = room->avatar(128);
|
||||
}
|
||||
@@ -240,15 +261,57 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
notification->sendEvent();
|
||||
}
|
||||
|
||||
void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QString &title, const QString &sender, const QImage &icon)
|
||||
void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom)
|
||||
{
|
||||
QPointer room(rawRoom);
|
||||
QPixmap img;
|
||||
img.convertFromImage(icon);
|
||||
|
||||
const auto roomMemberEvent = room->currentState().get<RoomMemberEvent>(room->localMember().id());
|
||||
if (roomMemberEvent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (NeoChatConfig::rejectUnknownInvites()) {
|
||||
auto job = room->connection()->callApi<NeochatGetCommonRoomsJob>(roomMemberEvent->senderId());
|
||||
connect(job, &BaseJob::result, this, [this, job, room] {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
if (replyData.contains(QStringLiteral("joined"))) {
|
||||
const bool inAnyOfOurRooms = !replyData[QStringLiteral("joined")].toArray().isEmpty();
|
||||
if (inAnyOfOurRooms) {
|
||||
doPostInviteNotification(room);
|
||||
} else {
|
||||
room->leaveRoom();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
doPostInviteNotification(room);
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
|
||||
{
|
||||
const auto roomMemberEvent = room->currentState().get<RoomMemberEvent>(room->localMember().id());
|
||||
if (roomMemberEvent == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto sender = room->member(roomMemberEvent->senderId());
|
||||
|
||||
QImage avatar_image;
|
||||
if (roomMemberEvent && !room->member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
avatar_image = room->member(roomMemberEvent->senderId()).avatar(128, 128, {});
|
||||
#else
|
||||
avatar_image = room->memberAvatar(roomMemberEvent->senderId()).get(room->connection(), 128, [] {});
|
||||
#endif
|
||||
} else {
|
||||
qWarning() << "using this room's avatar";
|
||||
avatar_image = room->avatar(128);
|
||||
}
|
||||
|
||||
KNotification *notification = new KNotification(QStringLiteral("invite"));
|
||||
notification->setText(i18n("%1 invited you to a room", sender));
|
||||
notification->setTitle(title);
|
||||
notification->setPixmap(createNotificationImage(icon, nullptr));
|
||||
notification->setText(i18n("%1 invited you to a room", sender.htmlSafeDisplayName()));
|
||||
notification->setTitle(room->displayName());
|
||||
notification->setPixmap(createNotificationImage(avatar_image, nullptr));
|
||||
auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
|
||||
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
|
||||
if (!room) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user