Compare commits
1 Commits
work/nvrwh
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb63ac5642 |
@@ -3,4 +3,5 @@
|
||||
|
||||
[BlueprintSettings]
|
||||
kde/applications/neochat.packageAppx=True
|
||||
kde/frameworks/extra-cmake-modules.version=master
|
||||
libs/qt.qtMajorVersion=6
|
||||
|
||||
@@ -10,8 +10,7 @@ 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
|
||||
@@ -39,8 +39,6 @@ include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
include(ECMAddAndroidApk)
|
||||
include(ECMQmlModule)
|
||||
include(GenerateExportHeader)
|
||||
include(ECMGenerateHeaders)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
@@ -103,7 +101,7 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -55,6 +55,5 @@
|
||||
|
||||
<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,9 +82,3 @@ 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,6 +14,7 @@ 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();
|
||||
@@ -22,19 +23,20 @@ private Q_SLOTS:
|
||||
void ActionsHandlerTest::nullObject()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(nullptr, nullptr);
|
||||
actionsHandler->handleMessageEvent(nullptr);
|
||||
|
||||
auto chatBarCache = new ChatBarCache(this);
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(nullptr, chatBarCache);
|
||||
actionsHandler->handleMessageEvent(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(room, nullptr);
|
||||
actionsHandler->handleMessageEvent(nullptr);
|
||||
|
||||
// The final one should throw no warning so we make sure.
|
||||
QTest::failOnWarning("ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(room, chatBarCache);
|
||||
actionsHandler->handleMessageEvent(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->relationAuthor(), room->member(QString()));
|
||||
QCOMPARE(chatBarCache->relationUser(), 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->relationAuthor(), Quotient::RoomMember());
|
||||
QCOMPARE(chatBarCache->relationUser(), 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->relationAuthor(), Quotient::RoomMember());
|
||||
QCOMPARE(chatBarCache->relationUser(), 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->relationAuthor(), room->member(QLatin1String("@example:example.org")));
|
||||
QCOMPARE(chatBarCache->relationUser(), 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->relationAuthor(), room->member(QLatin1String("@example:example.org")));
|
||||
QCOMPARE(chatBarCache->relationUser(), 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->relationAuthor(), room->member(QString()));
|
||||
QCOMPARE(chatBarCache->relationUser(), room->member(QString()));
|
||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
|
||||
}
|
||||
|
||||
@@ -213,13 +213,11 @@ void EventHandlerTest::genericBody_data()
|
||||
QTest::addColumn<int>("eventNum");
|
||||
QTest::addColumn<QString>("output");
|
||||
|
||||
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("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("reaction") << 3 << QStringLiteral("Unknown event");
|
||||
QTest::newRow("video") << 4 << QStringLiteral("<a href=\"https://matrix.to/#/@example:example.org\">after</a> sent a message");
|
||||
QTest::newRow("video") << 4 << QStringLiteral("sent a message");
|
||||
}
|
||||
|
||||
void EventHandlerTest::genericBody()
|
||||
@@ -227,16 +225,13 @@ void EventHandlerTest::genericBody()
|
||||
QFETCH(int, eventNum);
|
||||
QFETCH(QString, output);
|
||||
|
||||
QCOMPARE(EventHandler::genericBody(room, room->messageEvents().at(eventNum).get()), output);
|
||||
QCOMPARE(EventHandler::genericBody(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(room, nullptr), QString());
|
||||
QCOMPARE(EventHandler::genericBody(nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::markdownBody()
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <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,7 +12,9 @@
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
@@ -44,7 +46,6 @@ private Q_SLOTS:
|
||||
void sendCustomEmojiCode_data();
|
||||
void sendCustomEmojiCode();
|
||||
|
||||
void receiveSpacelessSelfClosingTag();
|
||||
void receiveStripReply();
|
||||
void receivePlainTextIn();
|
||||
|
||||
@@ -84,11 +85,11 @@ void TextHandlerTest::initTestCase()
|
||||
|
||||
void TextHandlerTest::allowedAttributes()
|
||||
{
|
||||
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>");
|
||||
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>");
|
||||
// Handle urls where the href has either single (') or double (") quotes.
|
||||
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>");
|
||||
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>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString1);
|
||||
@@ -116,7 +117,7 @@ void TextHandlerTest::stripDisallowedTags()
|
||||
void TextHandlerTest::stripDisallowedAttributes()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p style=\"font-size:50px;\" color=#FFFFFF>Test</p>");
|
||||
const QString testOutputString = QStringLiteral("Test");
|
||||
const QString testOutputString = QStringLiteral("<p>Test</p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -143,8 +144,8 @@ void TextHandlerTest::emptyCodeTags()
|
||||
|
||||
void TextHandlerTest::sendSimpleStringCase()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("This data should just be left alone.");
|
||||
const QString testOutputString = QStringLiteral("This data should just be left alone.");
|
||||
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>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -157,8 +158,8 @@ void TextHandlerTest::sendSingleParaMarkup()
|
||||
const QString testInputString = QStringLiteral(
|
||||
"Text para with **bold**, *italic*, [link](https://kde.org), , `inline code`.");
|
||||
const QString testOutputString = QStringLiteral(
|
||||
"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>.</p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -184,7 +185,7 @@ void TextHandlerTest::sendMultipleSectionMarkup()
|
||||
void TextHandlerTest::sendBadLinks()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("[link](kde.org), ");
|
||||
const QString testOutputString = QStringLiteral("<a>link</a>, <img alt=\"image\">");
|
||||
const QString testOutputString = QStringLiteral("<p><a>link</a>, <img alt=\"image\"></p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -221,8 +222,8 @@ void TextHandlerTest::sendCodeClass()
|
||||
void TextHandlerTest::sendCustomEmoji()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(":test:");
|
||||
const QString testOutputString =
|
||||
QStringLiteral("<img data-mx-emoticon=\"\" src=\"mxc://example.org/test\" alt=\":test:\" title=\":test:\" height=\"32\" vertical-align=\"middle\" />");
|
||||
const QString testOutputString = QStringLiteral(
|
||||
"<p><img data-mx-emoticon=\"\" src=\"mxc://example.org/test\" alt=\":test:\" title=\":test:\" height=\"32\" vertical-align=\"middle\" /></p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -235,7 +236,7 @@ void TextHandlerTest::sendCustomEmojiCode_data()
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("inline") << QStringLiteral("`:test:`") << QStringLiteral("<code>:test:</code>");
|
||||
QTest::newRow("inline") << QStringLiteral("`:test:`") << QStringLiteral("<p><code>:test:</code></p>");
|
||||
QTest::newRow("block") << QStringLiteral("```\n:test:\n```") << QStringLiteral("<pre><code>:test:\n</code></pre>");
|
||||
}
|
||||
|
||||
@@ -251,19 +252,6 @@ void TextHandlerTest::sendCustomEmojiCode()
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveSpacelessSelfClosingTag()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("Test...<br/>...ing");
|
||||
const QString testRichOutputString = QStringLiteral("Test...<br/>...ing");
|
||||
const QString testPlainOutputString = QStringLiteral("Test...\n...ing");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testRichOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testPlainOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveStripReply()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(
|
||||
@@ -373,7 +361,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("<b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b>");
|
||||
const QString testOutputString = QStringLiteral("<p><b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b></p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -384,7 +372,7 @@ void TextHandlerTest::receiveRichUserPill()
|
||||
void TextHandlerTest::receiveRichStrikethrough()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("<p><del>Test</del></p>");
|
||||
const QString testOutputString = QStringLiteral("<s>Test</s>");
|
||||
const QString testOutputString = QStringLiteral("<p><s>Test</s></p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -461,9 +449,6 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
QString testOutputStringMxId = QStringLiteral(
|
||||
"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>");
|
||||
|
||||
QString testInputStringMxIdWithPrefix = QStringLiteral("a @user:kde.org b");
|
||||
QString testOutputStringMxIdWithPrefix = QStringLiteral("a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputStringLink1);
|
||||
|
||||
@@ -477,9 +462,6 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
|
||||
testTextHandler.setData(testInputStringMxId);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
|
||||
|
||||
testTextHandler.setData(testInputStringMxIdWithPrefix);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxIdWithPrefix);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited_data()
|
||||
@@ -554,7 +536,7 @@ void TextHandlerTest::componentOutput_data()
|
||||
"someField }\nCustomQml {\n someTextProperty: someField.text\n}\n</code></pre>Sure you can, it's still local to the same file where you "
|
||||
"defined the id")
|
||||
<< QList<MessageComponent>{
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like<br/>"), {}},
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like"), {}},
|
||||
MessageComponent{
|
||||
MessageComponentType::Code,
|
||||
QStringLiteral(
|
||||
|
||||
@@ -17,6 +17,7 @@ class WindowControllerTest : public QObject
|
||||
|
||||
private Q_SLOTS:
|
||||
void nullWindow();
|
||||
void geometry();
|
||||
void showAndRaise();
|
||||
void toggle();
|
||||
|
||||
@@ -29,10 +30,32 @@ void WindowControllerTest::nullWindow()
|
||||
auto &instance = WindowController::instance();
|
||||
QCOMPARE(instance.window(), nullptr);
|
||||
|
||||
instance.restoreGeometry();
|
||||
instance.saveGeometry();
|
||||
instance.showAndRaiseWindow({});
|
||||
instance.toggleWindow();
|
||||
}
|
||||
|
||||
void WindowControllerTest::geometry()
|
||||
{
|
||||
auto &instance = WindowController::instance();
|
||||
|
||||
QWindow window;
|
||||
window.setGeometry(0, 0, 200, 200);
|
||||
instance.setWindow(&window);
|
||||
QCOMPARE(instance.window(), &window);
|
||||
|
||||
instance.saveGeometry();
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
|
||||
QCOMPARE(KWindowConfig::hasSavedWindowSize(windowGroup), true);
|
||||
|
||||
window.setGeometry(0, 0, 400, 400);
|
||||
QCOMPARE(window.geometry(), QRect(0, 0, 400, 400));
|
||||
instance.restoreGeometry();
|
||||
QCOMPARE(window.geometry(), QRect(0, 0, 200, 200));
|
||||
}
|
||||
|
||||
void WindowControllerTest::showAndRaise()
|
||||
{
|
||||
auto &instance = WindowController::instance();
|
||||
|
||||
@@ -55,8 +55,6 @@
|
||||
<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="de">Mit den Freunden auf Matrix unterhalten</summary>
|
||||
<summary xml:lang="el">Συνομιλήστε με τους φίλους σας στο matrix</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>
|
||||
@@ -87,8 +85,6 @@
|
||||
<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>
|
||||
@@ -115,8 +111,6 @@
|
||||
<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>
|
||||
@@ -146,8 +140,6 @@
|
||||
<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>
|
||||
@@ -179,7 +171,6 @@
|
||||
<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>
|
||||
@@ -209,7 +200,6 @@
|
||||
<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>
|
||||
@@ -240,7 +230,6 @@
|
||||
<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>
|
||||
@@ -305,8 +294,6 @@
|
||||
<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>
|
||||
@@ -340,8 +327,6 @@
|
||||
<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>
|
||||
@@ -378,8 +363,6 @@
|
||||
<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>
|
||||
@@ -414,8 +397,6 @@
|
||||
<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>
|
||||
@@ -448,8 +429,6 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<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"/>
|
||||
<release version="24.05.1" date="2024-06-13"/>
|
||||
|
||||
1725
po/ar/neochat.po
1725
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1510
po/ast/neochat.po
1510
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
1782
po/az/neochat.po
1782
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1687
po/ca/neochat.po
1687
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1559
po/cs/neochat.po
1559
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1661
po/da/neochat.po
1661
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
3025
po/de/neochat.po
3025
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
2308
po/el/neochat.po
2308
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1652
po/en_GB/neochat.po
1652
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1621
po/eo/neochat.po
1621
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1690
po/es/neochat.po
1690
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1711
po/eu/neochat.po
1711
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1808
po/fi/neochat.po
1808
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1697
po/fr/neochat.po
1697
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1682
po/gl/neochat.po
1682
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
1807
po/hu/neochat.po
1807
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1707
po/ia/neochat.po
1707
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1810
po/id/neochat.po
1810
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1710
po/ie/neochat.po
1710
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1690
po/it/neochat.po
1690
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1508
po/ja/neochat.po
1508
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1686
po/ka/neochat.po
1686
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1801
po/ko/neochat.po
1801
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1513
po/lt/neochat.po
1513
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
2370
po/lv/neochat.po
2370
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1688
po/nl/neochat.po
1688
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1686
po/nn/neochat.po
1686
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1730
po/pa/neochat.po
1730
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1692
po/pl/neochat.po
1692
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1808
po/pt/neochat.po
1808
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1774
po/pt_BR/neochat.po
1774
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1817
po/ru/neochat.po
1817
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1796
po/sk/neochat.po
1796
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -1,122 +0,0 @@
|
||||
<?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>
|
||||
1697
po/sl/neochat.po
1697
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1694
po/sv/neochat.po
1694
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1895
po/ta/neochat.po
1895
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1689
po/tok/neochat.po
1689
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
5
po/tr/docs/neochat/CMakeLists.txt
Normal file
5
po/tr/docs/neochat/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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})
|
||||
1685
po/tr/neochat.po
1685
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1701
po/uk/neochat.po
1701
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1577
po/zh_CN/neochat.po
1577
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1684
po/zh_TW/neochat.po
1684
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
158
snapcraft.yaml
158
snapcraft.yaml
@@ -1,158 +0,0 @@
|
||||
# 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,8 +194,6 @@ 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
|
||||
@@ -240,6 +238,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/MessageSourceSheet.qml
|
||||
@@ -306,12 +306,8 @@ add_subdirectory(devtools)
|
||||
add_subdirectory(login)
|
||||
add_subdirectory(chatbar)
|
||||
|
||||
if(NOT ANDROID AND NOT WIN32)
|
||||
qt_target_qml_sources(neochat QML_FILES
|
||||
qml/ShareAction.qml
|
||||
qml/GlobalMenu.qml
|
||||
qml/EditMenu.qml
|
||||
)
|
||||
if(UNIX)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
||||
else()
|
||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||
QT_RESOURCE_ALIAS qml/ShareAction.qml
|
||||
@@ -389,7 +385,7 @@ if(NOT ANDROID)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_ICU)
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
|
||||
target_sources(neochat PRIVATE runner.cpp)
|
||||
@@ -445,11 +441,8 @@ 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-symbolic"
|
||||
"arrow-up-symbolic"
|
||||
"arrow-up-double-symbolic"
|
||||
"arrow-left-symbolic"
|
||||
"arrow-right-symbolic"
|
||||
"arrow-down"
|
||||
"arrow-up"
|
||||
"checkmark"
|
||||
"help-about"
|
||||
"im-user"
|
||||
@@ -458,7 +451,6 @@ if(ANDROID)
|
||||
"mail-attachment"
|
||||
"dialog-cancel"
|
||||
"preferences-desktop-emoticons"
|
||||
"preferences-security"
|
||||
"document-open"
|
||||
"document-save"
|
||||
"document-send"
|
||||
|
||||
@@ -1,48 +1,70 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "actionshandler.h"
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include <Quotient/csapi/joining.h>
|
||||
#include <Quotient/events/roommemberevent.h>
|
||||
|
||||
#include <cmark.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <QStringBuilder>
|
||||
|
||||
#include "models/actionsmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
void ActionsHandler::handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache)
|
||||
ActionsHandler::ActionsHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
if (room == nullptr || chatBarCache == nullptr) {
|
||||
}
|
||||
|
||||
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) {
|
||||
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();
|
||||
room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
|
||||
m_room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
|
||||
chatBarCache->setAttachmentPath({});
|
||||
chatBarCache->setText({});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto handledText = handleMentions(chatBarCache);
|
||||
const auto result = handleQuickEdit(room, handledText);
|
||||
if (!result) {
|
||||
handleMessage(room, handledText, chatBarCache);
|
||||
}
|
||||
QString handledText = chatBarCache->text();
|
||||
handledText = handleMentions(handledText, chatBarCache->mentions());
|
||||
handleMessage(chatBarCache->text(), handledText, chatBarCache);
|
||||
}
|
||||
|
||||
QString ActionsHandler::handleMentions(ChatBarCache *chatBarCache)
|
||||
QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *mentions)
|
||||
{
|
||||
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;
|
||||
@@ -56,64 +78,48 @@ QString ActionsHandler::handleMentions(ChatBarCache *chatBarCache)
|
||||
return handledText;
|
||||
}
|
||||
|
||||
bool ActionsHandler::handleQuickEdit(NeoChatRoom *room, const QString &handledText)
|
||||
void ActionsHandler::handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(m_room);
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
|
||||
auto match = sed.match(handledText);
|
||||
auto match = sed.match(text);
|
||||
if (match.hasMatch()) {
|
||||
const QString regex = match.captured(1);
|
||||
const QString replacement = match.captured(2).toHtmlEscaped();
|
||||
const QString flags = match.captured(3);
|
||||
|
||||
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
|
||||
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
|
||||
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
|
||||
if (event->senderId() == room->localMember().id() && event->hasTextContent()) {
|
||||
if (event->senderId() == m_room->localMember().id() && event->hasTextContent()) {
|
||||
QString originalString;
|
||||
if (event->content()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content().get())->body;
|
||||
#else
|
||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
|
||||
#endif
|
||||
} else {
|
||||
originalString = event->plainBody();
|
||||
}
|
||||
if (flags == "/g"_L1) {
|
||||
room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
|
||||
if (flags == "/g"_ls) {
|
||||
m_room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
|
||||
} else {
|
||||
room->postHtmlMessage(handledText,
|
||||
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
||||
event->msgtype(),
|
||||
{},
|
||||
event->id());
|
||||
m_room->postHtmlMessage(handledText,
|
||||
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
||||
event->msgtype(),
|
||||
{},
|
||||
event->id());
|
||||
}
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActionsHandler::handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto messageType = RoomMessageEvent::MsgType::Text;
|
||||
|
||||
if (handledText.startsWith(QLatin1Char('/'))) {
|
||||
for (const auto &action : ActionsModel::instance().allActions()) {
|
||||
if (handledText.indexOf(action.prefix) == 1
|
||||
&& (handledText.indexOf(" "_ls) == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
|
||||
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), room, chatBarCache);
|
||||
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room, chatBarCache);
|
||||
if (action.messageType.has_value()) {
|
||||
messageType = *action.messageType;
|
||||
}
|
||||
@@ -130,11 +136,35 @@ void ActionsHandler::handleMessage(NeoChatRoom *room, QString handledText, ChatB
|
||||
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;
|
||||
}
|
||||
|
||||
room->postMessage(chatBarCache->text(), handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_actionshandler.cpp"
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
class ChatBarCache;
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ActionsHandler
|
||||
*
|
||||
* This class contains functions to handle chat messages ready for posting to a room.
|
||||
* This class handles chat messages ready for posting to a room.
|
||||
*
|
||||
* Everything that needs to be done to prepare the message for posting in a room
|
||||
* including:
|
||||
@@ -27,17 +31,36 @@ class NeoChatRoom;
|
||||
*
|
||||
* @sa ActionsModel, NeoChatRoom
|
||||
*/
|
||||
class ActionsHandler
|
||||
class ActionsHandler : public QObject
|
||||
{
|
||||
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.
|
||||
*/
|
||||
static void handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache);
|
||||
void handleMessageEvent(ChatBarCache *chatBarCache);
|
||||
|
||||
private:
|
||||
static QString handleMentions(ChatBarCache *chatBarCache);
|
||||
static bool handleQuickEdit(NeoChatRoom *room, const QString &handledText);
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
void checkEffects(const QString &text);
|
||||
|
||||
static void handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache);
|
||||
QString handleMentions(QString handledText, QList<Mention> *mentions);
|
||||
void handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache);
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ ecm_add_qml_module(chatbar GENERATE_PLUGIN_SOURCE
|
||||
CompletionMenu.qml
|
||||
EmojiDelegate.qml
|
||||
EmojiGrid.qml
|
||||
ReplyPane.qml
|
||||
PieProgressBar.qml
|
||||
EmojiPicker.qml
|
||||
EmojiDialog.qml
|
||||
|
||||
@@ -53,6 +53,14 @@ 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.
|
||||
*
|
||||
@@ -167,7 +175,6 @@ 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
|
||||
@@ -354,32 +361,15 @@ QQC2.Control {
|
||||
|
||||
Component {
|
||||
id: replyPane
|
||||
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
|
||||
ReplyPane {
|
||||
userName: _private.chatBarCache.relationUser.displayName
|
||||
userColor: _private.chatBarCache.relationUser.color
|
||||
userAvatar: _private.chatBarCache.relationUser.avatarUrl
|
||||
text: _private.chatBarCache.relationMessage
|
||||
|
||||
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
|
||||
onCancel: {
|
||||
_private.chatBarCache.replyId = "";
|
||||
_private.chatBarCache.attachmentPath = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -401,7 +391,7 @@ QQC2.Control {
|
||||
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
|
||||
|
||||
function postMessage() {
|
||||
_private.chatBarCache.postMessage();
|
||||
root.actionsHandler.handleMessageEvent(_private.chatBarCache);
|
||||
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-symbolic"
|
||||
source: "arrow-down"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
visible: root.showTones
|
||||
|
||||
98
src/chatbar/ReplyPane.qml
Normal file
98
src/chatbar/ReplyPane.qml
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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,7 +5,6 @@
|
||||
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
#include "actionshandler.h"
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "eventhandler.h"
|
||||
#include "neochatroom.h"
|
||||
@@ -54,7 +53,6 @@ void ChatBarCache::setReplyId(const QString &replyId)
|
||||
m_relationType = Reply;
|
||||
}
|
||||
m_attachmentPath = QString();
|
||||
delete m_relationContentModel;
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
Q_EMIT attachmentPathChanged();
|
||||
}
|
||||
@@ -84,12 +82,11 @@ 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::relationAuthor() const
|
||||
Quotient::RoomMember ChatBarCache::relationUser() const
|
||||
{
|
||||
if (parent() == nullptr) {
|
||||
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
|
||||
@@ -127,28 +124,6 @@ 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();
|
||||
@@ -181,7 +156,6 @@ 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);
|
||||
}
|
||||
@@ -191,7 +165,6 @@ 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();
|
||||
@@ -260,15 +233,4 @@ 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,8 +8,6 @@
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextCursor>
|
||||
|
||||
#include "models/messagecontentmodel.h"
|
||||
|
||||
class ChatDocumentHandler;
|
||||
|
||||
namespace Quotient
|
||||
@@ -102,7 +100,7 @@ class ChatBarCache : public QObject
|
||||
*
|
||||
* @sa Quotient::RoomMember
|
||||
*/
|
||||
Q_PROPERTY(Quotient::RoomMember relationAuthor READ relationAuthor NOTIFY relationIdChanged)
|
||||
Q_PROPERTY(Quotient::RoomMember relationUser READ relationUser NOTIFY relationIdChanged)
|
||||
|
||||
/**
|
||||
* @brief The content of the related message.
|
||||
@@ -111,13 +109,6 @@ 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.
|
||||
*/
|
||||
@@ -163,10 +154,9 @@ public:
|
||||
QString editId() const;
|
||||
void setEditId(const QString &editId);
|
||||
|
||||
Quotient::RoomMember relationAuthor() const;
|
||||
Quotient::RoomMember relationUser() const;
|
||||
|
||||
QString relationMessage() const;
|
||||
MessageContentModel *relationEventContentModel();
|
||||
|
||||
bool isThreaded() const;
|
||||
QString threadId() const;
|
||||
@@ -202,11 +192,6 @@ 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);
|
||||
@@ -221,6 +206,4 @@ private:
|
||||
QString m_attachmentPath = QString();
|
||||
QList<Mention> m_mentions;
|
||||
QString m_savedText;
|
||||
|
||||
QPointer<MessageContentModel> m_relationContentModel;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <Quotient/settings.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "proxycontroller.h"
|
||||
@@ -169,10 +168,6 @@ 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();
|
||||
|
||||
@@ -183,8 +178,6 @@ 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);
|
||||
}
|
||||
@@ -231,7 +224,7 @@ void Controller::invokeLogin()
|
||||
}
|
||||
});
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error), {});
|
||||
});
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection->assumeIdentity(account.userId(), account.deviceId(), accessToken);
|
||||
@@ -257,17 +250,17 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QStrin
|
||||
|
||||
switch (job->error()) {
|
||||
case QKeychain::EntryNotFound:
|
||||
Q_EMIT errorOccured(i18n("Access token wasn't found: Maybe it was deleted?"));
|
||||
Q_EMIT errorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
|
||||
break;
|
||||
case QKeychain::AccessDeniedByUser:
|
||||
case QKeychain::AccessDenied:
|
||||
Q_EMIT errorOccured(i18n("Access to keychain was denied: Please allow NeoChat to read the access token"));
|
||||
Q_EMIT errorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
|
||||
break;
|
||||
case QKeychain::NoBackendAvailable:
|
||||
Q_EMIT errorOccured(i18n("No keychain available: Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
|
||||
Q_EMIT errorOccured(i18n("No keychain available."), i18n("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: %1", job->errorString()));
|
||||
Q_EMIT errorOccured(i18n("Unable to read access token"), job->errorString());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -332,18 +325,11 @@ 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);
|
||||
@@ -358,7 +344,7 @@ void Controller::listenForNotifications()
|
||||
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
|
||||
|
||||
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
|
||||
instance().m_notificationsManager.postPushNotification(data);
|
||||
NotificationsManager::instance().postPushNotification(data);
|
||||
timer->stop();
|
||||
});
|
||||
|
||||
@@ -370,11 +356,6 @@ 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,7 +7,6 @@
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include <Quotient/accountregistry.h>
|
||||
|
||||
class TrayIcon;
|
||||
@@ -54,6 +53,16 @@ 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 *)
|
||||
{
|
||||
@@ -89,13 +98,6 @@ 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();
|
||||
@@ -131,20 +133,16 @@ 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:
|
||||
/**
|
||||
* @brief Request a error message be shown to the user.
|
||||
*/
|
||||
void errorOccured(const QString &error);
|
||||
void errorOccured(const QString &error, const QString &detail);
|
||||
void connectionAdded(NeoChatConnection *connection);
|
||||
void connectionDropped(NeoChatConnection *connection);
|
||||
void activeConnectionChanged(NeoChatConnection *connection);
|
||||
void accountsLoadingChanged();
|
||||
void showMessage(MessageType messageType, const QString &message);
|
||||
};
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
#include "models/emojimodel.h"
|
||||
|
||||
QMultiHash<QString, QVariant> EmojiTones::_tones = {
|
||||
// #include "emojitones_data.h"
|
||||
#include "emojitones_data.h"
|
||||
};
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <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,7 +10,6 @@
|
||||
|
||||
#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>
|
||||
@@ -34,21 +33,6 @@
|
||||
|
||||
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) {
|
||||
@@ -170,8 +154,7 @@ bool EventHandler::isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *
|
||||
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(event)) {
|
||||
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::self()->showLeaveJoinEvent()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isRename() && roomMemberEvent->prevContent() && roomMemberEvent->prevContent()->membership == roomMemberEvent->membership()
|
||||
&& !NeoChatConfig::self()->showRename()) {
|
||||
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::self()->showRename()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave()
|
||||
&& !NeoChatConfig::self()->showAvatarUpdate()) {
|
||||
@@ -228,11 +211,7 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
if (event.hasFileContent()) {
|
||||
// if filename is given or body is equal to filename,
|
||||
// then body is a caption
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString filename = event.fileContent()->originalName;
|
||||
#else
|
||||
QString filename = event.content()->fileInfo()->originalName;
|
||||
#endif
|
||||
QString body = event.plainBody();
|
||||
if (filename.isEmpty() || filename == body) {
|
||||
return QString();
|
||||
@@ -241,11 +220,7 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
}
|
||||
|
||||
if (event.hasTextContent() && event.content()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
body = event.richTextContent()->body;
|
||||
#else
|
||||
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
||||
#endif
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
}
|
||||
@@ -301,7 +276,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.toHtmlEscaped());
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>") : i18n("<i>[This message was deleted: %1]</i>", reason);
|
||||
}
|
||||
|
||||
const bool prettyPrint = (format == Qt::RichText);
|
||||
@@ -316,7 +291,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 = prettyPrint ? room->member(e.userId()).htmlSafeDisplayName() : room->member(e.userId()).displayName();
|
||||
auto subjectName = room->member(e.userId()).htmlSafeDisplayName();
|
||||
if (e.membership() == Membership::Leave) {
|
||||
if (e.prevContent() && e.prevContent()->displayName) {
|
||||
subjectName = sanitized(*e.prevContent()->displayName);
|
||||
@@ -473,14 +448,10 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
TextHandler textHandler;
|
||||
|
||||
if (event.hasFileContent()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString fileCaption = event.fileContent()->originalName;
|
||||
#else
|
||||
QString fileCaption = event.content()->fileInfo()->originalName;
|
||||
#endif
|
||||
auto fileCaption = event.content()->fileInfo()->originalName;
|
||||
if (fileCaption.isEmpty()) {
|
||||
fileCaption = event.plainBody();
|
||||
} else if (fileCaption != event.plainBody()) {
|
||||
} else if (event.content()->fileInfo()->originalName != event.plainBody()) {
|
||||
fileCaption = event.plainBody() + " | "_ls + fileCaption;
|
||||
}
|
||||
textHandler.setData(fileCaption);
|
||||
@@ -489,11 +460,7 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
|
||||
QString body;
|
||||
if (event.hasTextContent() && event.content()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
body = event.richTextContent()->body;
|
||||
#else
|
||||
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
||||
#endif
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
}
|
||||
@@ -514,12 +481,8 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
}
|
||||
}
|
||||
|
||||
QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
QString EventHandler::genericBody(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 {};
|
||||
@@ -528,149 +491,123 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
|
||||
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,
|
||||
[senderString](const RoomMessageEvent &) {
|
||||
return i18n("%1 sent a message", senderString);
|
||||
[](const RoomMessageEvent &e) {
|
||||
Q_UNUSED(e)
|
||||
return i18n("sent a message");
|
||||
},
|
||||
[senderString](const StickerEvent &) {
|
||||
return i18n("%1 sent a sticker", senderString);
|
||||
[](const StickerEvent &e) {
|
||||
Q_UNUSED(e)
|
||||
return i18n("sent a sticker");
|
||||
},
|
||||
[senderString](const RoomMemberEvent &e) {
|
||||
[](const RoomMemberEvent &e) {
|
||||
switch (e.membership()) {
|
||||
case Membership::Invite:
|
||||
if (e.repeatsState()) {
|
||||
return i18n("%1 reinvited someone to the room", senderString);
|
||||
return i18n("reinvited someone to the room");
|
||||
}
|
||||
Q_FALLTHROUGH();
|
||||
case Membership::Join: {
|
||||
QString text{};
|
||||
// Part 1: invites and joins
|
||||
if (e.repeatsState()) {
|
||||
return i18n("%1 joined the room (repeated)", senderString);
|
||||
text = i18n("joined the room (repeated)");
|
||||
} else if (e.changesMembership()) {
|
||||
return e.membership() == Membership::Invite ? i18n("%1 invited someone to the room", senderString)
|
||||
: i18n("%1 joined the room", senderString);
|
||||
text = e.membership() == Membership::Invite ? i18n("invited someone to the room") : i18n("joined the room");
|
||||
}
|
||||
if (!text.isEmpty()) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Part 2: profile changes of joined members
|
||||
MemberChanges changes = None;
|
||||
if (e.isRename()) {
|
||||
if (!e.newDisplayName()) {
|
||||
changes |= RemoveName;
|
||||
} else if (!e.prevContent()->displayName) {
|
||||
changes |= AddName;
|
||||
text = i18nc("their refers to a singular user", "cleared their display name");
|
||||
} else {
|
||||
changes |= Rename;
|
||||
text = i18nc("their refers to a singular user", "changed their display name");
|
||||
}
|
||||
}
|
||||
if (e.isAvatarUpdate()) {
|
||||
if (!text.isEmpty()) {
|
||||
text += i18n(" and ");
|
||||
}
|
||||
if (!e.newAvatarUrl()) {
|
||||
changes |= RemoveAvatar;
|
||||
text += i18nc("their refers to a singular user", "cleared their avatar");
|
||||
} else if (!e.prevContent()->avatarUrl) {
|
||||
changes |= AddAvatar;
|
||||
text += i18n("set an avatar");
|
||||
} else {
|
||||
changes |= UpdateAvatar;
|
||||
text += i18nc("their refers to a singular user", "updated their avatar");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if (text.isEmpty()) {
|
||||
text = i18nc("<user> changed nothing", "changed nothing");
|
||||
}
|
||||
|
||||
return i18nc("<user> changed nothing", "%1 changed nothing", senderString);
|
||||
return text;
|
||||
}
|
||||
case Membership::Leave:
|
||||
if (e.prevContent() && e.prevContent()->membership == Membership::Invite) {
|
||||
return (e.senderId() != e.userId()) ? i18n("%1 withdrew a user's invitation", senderString)
|
||||
: i18n("%1 rejected the invitation", senderString);
|
||||
return (e.senderId() != e.userId()) ? i18n("withdrew a user's invitation") : i18n("rejected the invitation");
|
||||
}
|
||||
|
||||
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
|
||||
return (e.senderId() != e.userId()) ? i18n("%1 unbanned a user", senderString) : i18n("%1 self-unbanned", senderString);
|
||||
return (e.senderId() != e.userId()) ? i18n("unbanned a user") : i18n("self-unbanned");
|
||||
}
|
||||
return (e.senderId() != e.userId()) ? i18n("%1 put a user out of the room", senderString) : i18n("%1 left the room", senderString);
|
||||
return (e.senderId() != e.userId()) ? i18n("put a user out of the room") : i18n("left the room");
|
||||
case Membership::Ban:
|
||||
if (e.senderId() != e.userId()) {
|
||||
return i18n("%1 banned a user from the room", senderString);
|
||||
return i18n("banned a user from the room");
|
||||
} else {
|
||||
return i18n("%1 self-banned from the room", senderString);
|
||||
return i18n("self-banned from the room");
|
||||
}
|
||||
case Membership::Knock: {
|
||||
return i18n("%1 requested an invite", senderString);
|
||||
return i18n("requested an invite");
|
||||
}
|
||||
default:;
|
||||
}
|
||||
return i18n("%1 made something unknown", senderString);
|
||||
return i18n("made something unknown");
|
||||
},
|
||||
[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 RoomCanonicalAliasEvent &e) {
|
||||
return (e.alias().isEmpty()) ? i18n("cleared the room main alias") : i18n("set the room main alias");
|
||||
},
|
||||
[senderString](const RoomNameEvent &e) {
|
||||
return (e.name().isEmpty()) ? i18n("%1 cleared the room name", senderString) : i18n("%1 set the room name", senderString);
|
||||
[](const RoomNameEvent &e) {
|
||||
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name");
|
||||
},
|
||||
[senderString](const RoomTopicEvent &e) {
|
||||
return (e.topic().isEmpty()) ? i18n("%1 cleared the topic", senderString) : i18n("%1 set the topic", senderString);
|
||||
[](const RoomTopicEvent &e) {
|
||||
return (e.topic().isEmpty()) ? i18n("cleared the topic") : i18n("set the topic");
|
||||
},
|
||||
[senderString](const RoomAvatarEvent &) {
|
||||
return i18n("%1 changed the room avatar", senderString);
|
||||
[](const RoomAvatarEvent &) {
|
||||
return i18n("changed the room avatar");
|
||||
},
|
||||
[senderString](const EncryptionEvent &) {
|
||||
return i18n("%1 activated End-to-End Encryption", senderString);
|
||||
[](const EncryptionEvent &) {
|
||||
return i18n("activated End-to-End Encryption");
|
||||
},
|
||||
[senderString](const RoomCreateEvent &e) {
|
||||
return e.isUpgrade() ? i18n("%1 upgraded the room version", senderString) : i18n("%1 created the room", senderString);
|
||||
[](const RoomCreateEvent &e) {
|
||||
return e.isUpgrade() ? i18n("upgraded the room version") : i18n("created the room");
|
||||
},
|
||||
[senderString](const RoomPowerLevelsEvent &) {
|
||||
return i18nc("'power level' means permission level", "%1 changed the power levels for this room", senderString);
|
||||
[](const RoomPowerLevelsEvent &) {
|
||||
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
||||
},
|
||||
[senderString](const LocationBeaconEvent &) {
|
||||
return i18n("%1 sent a live location beacon", senderString);
|
||||
[](const LocationBeaconEvent &) {
|
||||
return i18n("sent a live location beacon");
|
||||
},
|
||||
[senderString](const RoomServerAclEvent &) {
|
||||
return i18n("%1 changed the server access control lists for this room", senderString);
|
||||
[](const RoomServerAclEvent &) {
|
||||
return i18n("changed the server access control lists for this room");
|
||||
},
|
||||
[senderString](const WidgetEvent &e) {
|
||||
[](const WidgetEvent &e) {
|
||||
if (e.fullJson()["unsigned"_ls]["prev_content"_ls].toObject().isEmpty()) {
|
||||
return i18n("%1 added a widget", senderString);
|
||||
return i18n("added a widget");
|
||||
}
|
||||
if (e.contentJson().isEmpty()) {
|
||||
return i18n("%1 removed a widget", senderString);
|
||||
return i18n("removed a widget");
|
||||
}
|
||||
return i18n("%1 configured a widget", senderString);
|
||||
return i18n("configured a widget");
|
||||
},
|
||||
[senderString](const StateEvent &) {
|
||||
return i18n("%1 updated the state", senderString);
|
||||
[](const StateEvent &) {
|
||||
return i18n("updated the state");
|
||||
},
|
||||
[senderString](const PollStartEvent &) {
|
||||
return i18n("%1 started a poll", senderString);
|
||||
[](const PollStartEvent &e) {
|
||||
Q_UNUSED(e);
|
||||
return i18n("started a poll");
|
||||
},
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
@@ -712,39 +649,28 @@ QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Qu
|
||||
return {};
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const auto content = roomMessageEvent->content();
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, static_cast<EventContent::FileContent *>(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
|
||||
const EventContent::FileInfo *fileInfo;
|
||||
fileInfo = roomMessageEvent->content()->fileInfo();
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, fileInfo, eventId, false, false);
|
||||
// if filename isn't specifically given, it is in body
|
||||
// https://spec.matrix.org/latest/client-server-api/#mfile
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["filename"_ls] =
|
||||
(roomMessageEvent->fileContent()->originalName.isEmpty()) ? roomMessageEvent->plainBody() : roomMessageEvent->fileContent()->originalName;
|
||||
#else
|
||||
mediaInfo["filename"_ls] = (content->fileInfo()->originalName.isEmpty()) ? roomMessageEvent->plainBody() : content->fileInfo()->originalName;
|
||||
#endif
|
||||
mediaInfo["filename"_ls] = (fileInfo->originalName.isEmpty()) ? roomMessageEvent->plainBody() : fileInfo->originalName;
|
||||
|
||||
return mediaInfo;
|
||||
} else if (event->is<StickerEvent>()) {
|
||||
auto stickerEvent = eventCast<const StickerEvent>(event);
|
||||
auto content = &stickerEvent->image();
|
||||
const EventContent::FileInfo *fileInfo;
|
||||
|
||||
return getMediaInfoFromFileInfo(room, content, eventId, false, true);
|
||||
auto stickerEvent = eventCast<const StickerEvent>(event);
|
||||
fileInfo = &stickerEvent->image();
|
||||
|
||||
return getMediaInfoFromFileInfo(room, fileInfo, eventId, false, true);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const Quotient::EventContent::FileContentBase *fileContent,
|
||||
#else
|
||||
const Quotient::EventContent::TypedBase *fileContent,
|
||||
#endif
|
||||
const EventContent::FileInfo *fileInfo,
|
||||
const QString &eventId,
|
||||
bool isThumbnail,
|
||||
bool isSticker)
|
||||
@@ -752,18 +678,10 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
// Get the mxc URL for the media.
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (!fileContent->url().isValid() || fileContent->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
#else
|
||||
if (!fileContent->fileInfo()->url().isValid() || fileContent->fileInfo()->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
#endif
|
||||
if (!fileInfo->url().isValid() || fileInfo->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QUrl source = room->makeMediaUrl(eventId, fileContent->url());
|
||||
#else
|
||||
QUrl source = room->makeMediaUrl(eventId, fileContent->fileInfo()->url());
|
||||
#endif
|
||||
QUrl source = room->makeMediaUrl(eventId, fileInfo->url());
|
||||
|
||||
if (source.isValid()) {
|
||||
mediaInfo["source"_ls] = source;
|
||||
@@ -772,7 +690,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
}
|
||||
}
|
||||
|
||||
auto mimeType = fileContent->type();
|
||||
auto mimeType = fileInfo->mimeType;
|
||||
// Add the MIME type for the media if available.
|
||||
mediaInfo["mimeType"_ls] = mimeType.name();
|
||||
|
||||
@@ -780,53 +698,45 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
mediaInfo["mimeIcon"_ls] = mimeType.iconName();
|
||||
|
||||
// Add media size if available.
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["size"_ls] = static_cast<const EventContent::FileContent *>(fileContent)->payloadSize;
|
||||
#else
|
||||
mediaInfo["size"_ls] = static_cast<const EventContent::FileContent *>(fileContent)->fileInfo()->payloadSize;
|
||||
#endif
|
||||
mediaInfo["size"_ls] = fileInfo->payloadSize;
|
||||
|
||||
mediaInfo["isSticker"_ls] = isSticker;
|
||||
|
||||
// Add parameter depending on media type.
|
||||
if (mimeType.name().contains(QStringLiteral("image"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileContent)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileInfo)) {
|
||||
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());
|
||||
|
||||
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();
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(room, castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
tempInfo["source"_ls] = QUrl("image://blurhash/"_ls + blurhash);
|
||||
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);
|
||||
}
|
||||
}
|
||||
mediaInfo["tempInfo"_ls] = tempInfo;
|
||||
}
|
||||
mediaInfo["tempInfo"_ls] = tempInfo;
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("video"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::VideoContent *>(fileContent)) {
|
||||
if (auto castInfo = static_cast<const EventContent::VideoContent *>(fileInfo)) {
|
||||
mediaInfo["width"_ls] = castInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = castInfo->imageSize.height();
|
||||
mediaInfo["duration"_ls] = castInfo->duration;
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromTumbnail(room, castInfo->thumbnail, eventId);
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(room, castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
@@ -842,7 +752,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("audio"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::AudioContent *>(fileContent)) {
|
||||
if (auto castInfo = static_cast<const EventContent::AudioContent *>(fileInfo)) {
|
||||
mediaInfo["duration"_ls] = castInfo->duration;
|
||||
}
|
||||
}
|
||||
@@ -850,38 +760,6 @@ 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 NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
static QString genericBody(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Output a string for the event to be used as a RoomList subtitle.
|
||||
@@ -290,13 +290,8 @@ private:
|
||||
|
||||
static QVariantMap getMediaInfoForEvent(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
QVariantMap static getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const Quotient::EventContent::FileContentBase *fileContent,
|
||||
#else
|
||||
const Quotient::EventContent::TypedBase *fileContent,
|
||||
#endif
|
||||
const Quotient::EventContent::FileInfo *fileInfo,
|
||||
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 m_connection->errorOccured(i18n("Network Error: %1", std::move(error)));
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), 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 loginErrorOccured(i18n("Login Failed: %1", error));
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
}
|
||||
m_isLoggingIn = false;
|
||||
Q_EMIT isLoggingInChanged();
|
||||
});
|
||||
|
||||
connect(m_connection, &Connection::resolveError, this, [this](QString error) {
|
||||
Q_EMIT m_connection->errorOccured(i18n("Network Error: %1", std::move(error)));
|
||||
connect(m_connection, &Connection::resolveError, this, [](QString error) {
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error));
|
||||
});
|
||||
|
||||
connect(
|
||||
|
||||
@@ -130,7 +130,7 @@ Q_SIGNALS:
|
||||
void loginFlowsChanged();
|
||||
void ssoUrlChanged();
|
||||
void connected();
|
||||
void loginErrorOccured(const QString &message);
|
||||
void errorOccured(const QString &message);
|
||||
void testingChanged();
|
||||
void isLoggingInChanged();
|
||||
void isLoggedInChanged();
|
||||
|
||||
@@ -122,7 +122,7 @@ Kirigami.Page {
|
||||
|
||||
QQC2.ToolButton {
|
||||
text: i18nc("@action:button", "Log out of this account")
|
||||
icon.name: "im-kick-user"
|
||||
icon.name: "edit-delete-remove"
|
||||
onClicked: Controller.removeConnection(modelData)
|
||||
display: QQC2.Button.IconOnly
|
||||
QQC2.ToolTip.text: text
|
||||
@@ -214,7 +214,7 @@ Kirigami.Page {
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
|
||||
function onLoginErrorOccured(message) {
|
||||
function onErrorOccured(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-symbolic"
|
||||
icon.name: "arrow-right"
|
||||
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-symbolic"
|
||||
icon.name: "arrow-left"
|
||||
enabled: root.currentStep.previousAction ? root.currentStep.previousAction.enabled : false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "colorschemer.h"
|
||||
#include "controller.h"
|
||||
#include "logger.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "roommanager.h"
|
||||
#include "sharehandler.h"
|
||||
#include "windowcontroller.h"
|
||||
@@ -303,6 +304,7 @@ int main(int argc, char *argv[])
|
||||
QWindow *window = windowFromEngine(&engine);
|
||||
|
||||
WindowController::instance().setWindow(window);
|
||||
WindowController::instance().restoreGeometry();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "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,7 +4,7 @@
|
||||
#include "actionsmodel.h"
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "enums/messagetype.h"
|
||||
#include "controller.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
@@ -24,14 +24,15 @@ QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls
|
||||
|
||||
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18n("Leaving this room."));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, 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 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));
|
||||
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));
|
||||
return QString();
|
||||
}
|
||||
auto leaving = room->connection()->room(text);
|
||||
@@ -39,10 +40,10 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
|
||||
leaving = room->connection()->roomByAlias(text);
|
||||
}
|
||||
if (leaving) {
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||
room->connection()->leaveRoom(leaving);
|
||||
} else {
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
@@ -50,7 +51,7 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
|
||||
|
||||
auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
} else {
|
||||
room->connection()->user()->rename(text, room);
|
||||
}
|
||||
@@ -192,29 +193,31 @@ 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 room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
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));
|
||||
return QString();
|
||||
}
|
||||
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
|
||||
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
|
||||
Q_EMIT room->showMessage(MessageType::Information,
|
||||
i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info,
|
||||
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 room->showMessage(MessageType::Information, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
||||
return QString();
|
||||
}
|
||||
if (room->localMember().id() == text) {
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18n("You are already in this room."));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18n("You are already in this room."));
|
||||
return QString();
|
||||
}
|
||||
if (room->joinedMemberIds().contains(text)) {
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
|
||||
return QString();
|
||||
}
|
||||
room->inviteToRoom(text);
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> was invited into this room", "%1 was invited into this room", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||
i18nc("<username> was invited into this room", "%1 was invited into this room", text));
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -228,8 +231,9 @@ QList<ActionsModel::Action> actions{
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
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));
|
||||
return QString();
|
||||
}
|
||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||
@@ -237,7 +241,7 @@ QList<ActionsModel::Action> actions{
|
||||
RoomManager::instance().resolveResource(targetRoom->id());
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
RoomManager::instance().resolveResource(text, "join"_ls);
|
||||
return QString();
|
||||
},
|
||||
@@ -254,8 +258,9 @@ QList<ActionsModel::Action> actions{
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(roomName);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
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));
|
||||
return QString();
|
||||
}
|
||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||
@@ -263,7 +268,7 @@ QList<ActionsModel::Action> actions{
|
||||
RoomManager::instance().resolveResource(targetRoom->id());
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, 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) {
|
||||
@@ -284,15 +289,16 @@ QList<ActionsModel::Action> actions{
|
||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||
auto regexMatch = roomRegex.match(text);
|
||||
if (!regexMatch.hasMatch()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||
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));
|
||||
return QString();
|
||||
}
|
||||
if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
RoomManager::instance().resolveResource(text, "join"_ls);
|
||||
return QString();
|
||||
},
|
||||
@@ -321,7 +327,7 @@ QList<ActionsModel::Action> actions{
|
||||
QStringLiteral("nick"),
|
||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||
if (text.isEmpty()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("No new nickname provided, no changes will happen."));
|
||||
} else {
|
||||
room->connection()->user()->rename(text);
|
||||
}
|
||||
@@ -355,15 +361,16 @@ 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 room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
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));
|
||||
return QString();
|
||||
}
|
||||
if (room->connection()->ignoredUsers().contains(text)) {
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
|
||||
return QString();
|
||||
}
|
||||
room->connection()->addToIgnoredUsers(text);
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
|
||||
|
||||
return QString();
|
||||
},
|
||||
@@ -379,15 +386,16 @@ 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 room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
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));
|
||||
return QString();
|
||||
}
|
||||
if (!room->connection()->ignoredUsers().contains(text)) {
|
||||
Q_EMIT room->showMessage(MessageType::Information, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
|
||||
return QString();
|
||||
}
|
||||
room->connection()->removeFromIgnoredUsers(text);
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -423,13 +431,14 @@ 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 room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
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));
|
||||
return QString();
|
||||
}
|
||||
auto state = room->currentState().get<RoomMemberEvent>(parts[0]);
|
||||
if (state && state->membership() == Membership::Ban) {
|
||||
Q_EMIT room->showMessage(MessageType::Information,
|
||||
i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info,
|
||||
i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
|
||||
return QString();
|
||||
}
|
||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||
@@ -437,17 +446,18 @@ QList<ActionsModel::Action> actions{
|
||||
return QString();
|
||||
}
|
||||
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("You are not allowed to ban users from this room."));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::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 room->showMessage(
|
||||
MessageType::Error,
|
||||
Q_EMIT Controller::instance().showMessage(
|
||||
Controller::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 room->showMessage(MessageType::Positive, i18nc("<username> was banned from this room.", "%1 was banned from this room.", parts[0]));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||
i18nc("<username> was banned from this room.", "%1 was banned from this room.", parts[0]));
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -462,7 +472,8 @@ 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 room->showMessage(MessageType::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
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));
|
||||
return QString();
|
||||
}
|
||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||
@@ -470,16 +481,18 @@ QList<ActionsModel::Action> actions{
|
||||
return QString();
|
||||
}
|
||||
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("You are not allowed to unban users from this room."));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::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 room->showMessage(MessageType::Information, i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info,
|
||||
i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
|
||||
return QString();
|
||||
}
|
||||
room->unban(text);
|
||||
Q_EMIT room->showMessage(MessageType::Positive, i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||
i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
|
||||
|
||||
return QString();
|
||||
},
|
||||
@@ -496,16 +509,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 room->showMessage(MessageType::Error,
|
||||
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0]));
|
||||
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]));
|
||||
return QString();
|
||||
}
|
||||
if (parts[0] == room->localMember().id()) {
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("You cannot kick yourself from the room."));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("You cannot kick yourself from the room."));
|
||||
return QString();
|
||||
}
|
||||
if (!room->isMember(parts[0])) {
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
|
||||
return QString();
|
||||
}
|
||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||
@@ -514,17 +527,18 @@ QList<ActionsModel::Action> actions{
|
||||
}
|
||||
auto kick = plEvent->kick();
|
||||
if (plEvent->powerLevelForUser(room->localMember().id()) < kick) {
|
||||
Q_EMIT room->showMessage(MessageType::Error, i18n("You are not allowed to kick users from this room."));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::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 room->showMessage(
|
||||
MessageType::Error,
|
||||
Q_EMIT Controller::instance().showMessage(
|
||||
Controller::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 room->showMessage(MessageType::Positive, i18nc("<username> was kicked from this room.", "%1 was kicked from this room.", parts[0]));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||
i18nc("<username> was kicked from this room.", "%1 was kicked from this room.", parts[0]));
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
|
||||
@@ -29,6 +29,18 @@
|
||||
|
||||
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)
|
||||
@@ -42,137 +54,17 @@ MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &event
|
||||
void MessageContentModel::initializeModel()
|
||||
{
|
||||
Q_ASSERT(m_room != nullptr);
|
||||
Q_ASSERT(!m_eventId.isEmpty());
|
||||
// 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);
|
||||
|
||||
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;
|
||||
initializeEvent();
|
||||
intiializeEvent(m_room->getEvent(eventId));
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
return true;
|
||||
@@ -191,7 +83,130 @@ void MessageContentModel::getEvent()
|
||||
return false;
|
||||
});
|
||||
|
||||
m_room->downloadEventFromServer(m_eventId);
|
||||
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();
|
||||
}
|
||||
|
||||
bool MessageContentModel::showAuthor() const
|
||||
@@ -206,7 +221,7 @@ void MessageContentModel::setShowAuthor(bool showAuthor)
|
||||
}
|
||||
m_showAuthor = showAuthor;
|
||||
|
||||
if (m_room->connection()->isIgnored(m_eventSenderId)) {
|
||||
if (m_event != nullptr && m_room->connection()->isIgnored(m_event->senderId())) {
|
||||
if (showAuthor) {
|
||||
beginInsertRows({}, 0, 0);
|
||||
m_components.prepend(MessageComponent{MessageComponentType::Author, QString(), {}});
|
||||
@@ -235,23 +250,8 @@ 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_room->connection()->isIgnored(m_eventSenderId)) {
|
||||
if (m_notFound || (m_event && m_room->connection()->isIgnored(m_event->senderId()))) {
|
||||
Kirigami::Platform::PlatformTheme *theme =
|
||||
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
|
||||
@@ -265,17 +265,21 @@ 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) {
|
||||
if (m_isReply) {
|
||||
return i18n("Loading reply");
|
||||
} else {
|
||||
return i18n("Loading");
|
||||
}
|
||||
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.content.isEmpty()) {
|
||||
return component.content;
|
||||
}
|
||||
return EventHandler::richBody(m_room, event);
|
||||
return EventHandler::richBody(m_room, m_event.get());
|
||||
}
|
||||
if (role == ComponentTypeRole) {
|
||||
return component.type;
|
||||
@@ -284,53 +288,53 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return component.attributes;
|
||||
}
|
||||
if (role == EventIdRole) {
|
||||
return EventHandler::id(event);
|
||||
return EventHandler::id(m_event.get());
|
||||
}
|
||||
if (role == TimeRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
||||
return event->transactionId() == pendingEvent->transactionId();
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||
return m_event->transactionId() == pendingEvent->transactionId();
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return EventHandler::time(event, m_isPending, lastUpdated);
|
||||
return EventHandler::time(m_event.get(), m_isPending, lastUpdated);
|
||||
}
|
||||
if (role == TimeStringRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
|
||||
return event->transactionId() == pendingEvent->transactionId();
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||
return m_event->transactionId() == pendingEvent->transactionId();
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return EventHandler::timeString(event, QStringLiteral("hh:mm"), m_isPending, lastUpdated);
|
||||
return EventHandler::timeString(m_event.get(), QStringLiteral("hh:mm"), m_isPending, lastUpdated);
|
||||
}
|
||||
if (role == AuthorRole) {
|
||||
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
|
||||
}
|
||||
if (role == MediaInfoRole) {
|
||||
return EventHandler::mediaInfo(m_room, event);
|
||||
return EventHandler::mediaInfo(m_room, m_event.get());
|
||||
}
|
||||
if (role == FileTransferInfoRole) {
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(event));
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(m_event.get()));
|
||||
}
|
||||
if (role == ItineraryModelRole) {
|
||||
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
||||
}
|
||||
if (role == LatitudeRole) {
|
||||
return EventHandler::latitude(event);
|
||||
return EventHandler::latitude(m_event.get());
|
||||
}
|
||||
if (role == LongitudeRole) {
|
||||
return EventHandler::longitude(event);
|
||||
return EventHandler::longitude(m_event.get());
|
||||
}
|
||||
if (role == AssetRole) {
|
||||
return EventHandler::locationAssetType(event);
|
||||
return EventHandler::locationAssetType(m_event.get());
|
||||
}
|
||||
if (role == PollHandlerRole) {
|
||||
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
|
||||
}
|
||||
if (role == ReplyEventIdRole) {
|
||||
return EventHandler::replyId(event);
|
||||
return EventHandler::replyId(m_event.get());
|
||||
}
|
||||
if (role == ReplyAuthorRole) {
|
||||
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event));
|
||||
return QVariant::fromValue(EventHandler::replyAuthor(m_room, m_event.get()));
|
||||
}
|
||||
if (role == ReplyContentModelRole) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
||||
@@ -386,18 +390,16 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
|
||||
|
||||
void MessageContentModel::resetModel()
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
|
||||
beginResetModel();
|
||||
m_components.clear();
|
||||
|
||||
if (m_room->connection()->isIgnored(m_eventSenderId) || m_notFound) {
|
||||
if ((m_event && m_room->connection()->isIgnored(m_event->senderId())) || m_notFound) {
|
||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == nullptr) {
|
||||
if (m_event == nullptr) {
|
||||
m_components += MessageComponent{MessageComponentType::Loading, QString(), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
@@ -413,6 +415,8 @@ 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);
|
||||
@@ -429,20 +433,15 @@ 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>(event)
|
||||
&& eventCast<const Quotient::RoomMessageEvent>(event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
||||
if (eventCast<const Quotient::RoomMessageEvent>(m_event)
|
||||
&& eventCast<const Quotient::RoomMessageEvent>(m_event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
||||
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
|
||||
return newComponents;
|
||||
}
|
||||
|
||||
if (event->isRedacted()) {
|
||||
if (m_event->isRedacted()) {
|
||||
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
return newComponents;
|
||||
}
|
||||
@@ -454,7 +453,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
if (isEditing) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
} else {
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event)));
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*m_event.get())));
|
||||
}
|
||||
|
||||
if (m_room->urlPreviewEnabled()) {
|
||||
@@ -462,7 +461,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
}
|
||||
|
||||
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
|
||||
if (isThreading && !EventHandler::isThreaded(event)) {
|
||||
if (isThreading && !EventHandler::isThreaded(m_event.get())) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
}
|
||||
|
||||
@@ -471,12 +470,11 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
|
||||
void MessageContentModel::updateReplyModel()
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event == nullptr || m_isReply) {
|
||||
if (m_event == nullptr || m_isReply) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EventHandler::hasReply(event) || (EventHandler::isThreaded(event) && NeoChatConfig::self()->threads())) {
|
||||
if (!EventHandler::hasReply(m_event.get()) || (EventHandler::isThreaded(m_event.get()) && NeoChatConfig::self()->threads())) {
|
||||
if (m_replyModel) {
|
||||
delete m_replyModel;
|
||||
}
|
||||
@@ -487,7 +485,12 @@ void MessageContentModel::updateReplyModel()
|
||||
return;
|
||||
}
|
||||
|
||||
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event), true, false, this);
|
||||
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);
|
||||
}
|
||||
|
||||
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
|
||||
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
|
||||
@@ -496,45 +499,28 @@ 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 roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
return TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
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());
|
||||
}
|
||||
case MessageComponentType::File: {
|
||||
QList<MessageComponent> components;
|
||||
components += MessageComponent{MessageComponentType::File, QString(), {}};
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||
|
||||
if (m_emptyItinerary) {
|
||||
if (!m_isReply) {
|
||||
auto fileTransferInfo = m_room->cachedFileTransferInfo(event);
|
||||
auto fileTransferInfo = m_room->cachedFileTransferInfo(m_event.get());
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->hasFileContent());
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const QMimeType mimeType = roomMessageEvent->fileContent()->mimeType;
|
||||
#else
|
||||
const QMimeType mimeType = roomMessageEvent->content()->fileInfo()->mimeType;
|
||||
#endif
|
||||
Q_ASSERT(event->content() != nullptr && event->content()->fileInfo() != nullptr);
|
||||
const QMimeType mimeType = event->content()->fileInfo()->mimeType;
|
||||
if (mimeType.name() == QStringLiteral("text/plain") || mimeType.parentMimeTypes().contains(QStringLiteral("text/plain"))) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString originalName = roomMessageEvent->fileContent()->originalName;
|
||||
#else
|
||||
QString originalName = roomMessageEvent->content()->fileInfo()->originalName;
|
||||
#endif
|
||||
QString originalName = event->content()->fileInfo()->originalName;
|
||||
if (originalName.isEmpty()) {
|
||||
originalName = roomMessageEvent->plainBody();
|
||||
originalName = event->plainBody();
|
||||
}
|
||||
KSyntaxHighlighting::Repository repository;
|
||||
KSyntaxHighlighting::Definition definitionForFile = repository.definitionForFileName(originalName);
|
||||
@@ -563,27 +549,19 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
||||
} else {
|
||||
updateItineraryModel();
|
||||
}
|
||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
components += TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
auto body = EventHandler::rawMessageBody(*event);
|
||||
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
|
||||
return components;
|
||||
}
|
||||
case MessageComponentType::Image:
|
||||
case MessageComponentType::Audio:
|
||||
case MessageComponentType::Video: {
|
||||
if (!event->is<StickerEvent>()) {
|
||||
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
|
||||
if (!m_event->is<StickerEvent>()) {
|
||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||
QList<MessageComponent> components;
|
||||
components += MessageComponent{type, QString(), {}};
|
||||
auto body = EventHandler::rawMessageBody(*roomMessageEvent);
|
||||
components += TextHandler().textComponents(body,
|
||||
EventHandler::messageBodyInputFormat(*roomMessageEvent),
|
||||
m_room,
|
||||
roomMessageEvent,
|
||||
roomMessageEvent->isReplaced());
|
||||
auto body = EventHandler::rawMessageBody(*event);
|
||||
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
|
||||
return components;
|
||||
}
|
||||
}
|
||||
@@ -600,22 +578,21 @@ MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
|
||||
}
|
||||
if (linkPreviewer->loaded()) {
|
||||
return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_ls, link}}};
|
||||
} else {
|
||||
connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() {
|
||||
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
|
||||
for (auto &component : m_components) {
|
||||
if (component.attributes["link"_ls].toUrl() == link) {
|
||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||
beginResetModel();
|
||||
component.type = MessageComponentType::LinkPreview;
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if (component.attributes["link"_ls].toUrl() == link) {
|
||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||
beginResetModel();
|
||||
component.type = MessageComponentType::LinkPreview;
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_ls, link}}};
|
||||
}
|
||||
}
|
||||
});
|
||||
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_ls, link}}};
|
||||
}
|
||||
|
||||
QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageComponent> inputComponents)
|
||||
@@ -642,11 +619,6 @@ 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();
|
||||
@@ -658,14 +630,13 @@ void MessageContentModel::closeLinkPreview(int row)
|
||||
|
||||
void MessageContentModel::updateItineraryModel()
|
||||
{
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (m_room == nullptr || event == nullptr) {
|
||||
if (m_room == nullptr || m_event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
if (roomMessageEvent->hasFileContent()) {
|
||||
auto filePath = m_room->cachedFileTransferInfo(event).localPath;
|
||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||
if (event->hasFileContent()) {
|
||||
auto filePath = m_room->cachedFileTransferInfo(m_event.get()).localPath;
|
||||
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
|
||||
delete m_itineraryModel;
|
||||
m_itineraryModel = nullptr;
|
||||
|
||||
@@ -11,9 +11,24 @@
|
||||
|
||||
#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
|
||||
*
|
||||
@@ -60,10 +75,11 @@ public:
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit MessageContentModel(NeoChatRoom *room,
|
||||
const QString &eventId,
|
||||
const Quotient::RoomEvent *event,
|
||||
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);
|
||||
@@ -98,7 +114,6 @@ public:
|
||||
|
||||
Q_SIGNALS:
|
||||
void showAuthorChanged();
|
||||
void eventUnavailable();
|
||||
void eventUpdated();
|
||||
|
||||
private:
|
||||
@@ -106,6 +121,7 @@ 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;
|
||||
@@ -113,8 +129,8 @@ private:
|
||||
bool m_notFound = false;
|
||||
|
||||
void initializeModel();
|
||||
void initializeEvent();
|
||||
void getEvent();
|
||||
void intiializeEvent(const QString &eventId);
|
||||
void intiializeEvent(const Quotient::RoomEvent *event);
|
||||
|
||||
QList<MessageComponent> m_components;
|
||||
void resetModel();
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include <KFormat>
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
@@ -85,21 +84,19 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_currentRoom) {
|
||||
// HACK: Reset the model to a null room first to make sure QML dismantles
|
||||
// last room's objects before the room is actually changed
|
||||
beginResetModel();
|
||||
m_currentRoom->disconnect(this);
|
||||
m_currentRoom = nullptr;
|
||||
endResetModel();
|
||||
// HACK: Reset the model to a null room first to make sure QML dismantles
|
||||
// last room's objects before the room is actually changed
|
||||
beginResetModel();
|
||||
m_currentRoom->disconnect(this);
|
||||
m_currentRoom = nullptr;
|
||||
endResetModel();
|
||||
|
||||
// Don't clear the member objects until the model has been fully reset and all
|
||||
// refs cleared.
|
||||
m_memberObjects.clear();
|
||||
m_contentModels.clear();
|
||||
m_reactionModels.clear();
|
||||
m_readMarkerModels.clear();
|
||||
}
|
||||
// Don't clear the member objects until the model has been fully reset and all
|
||||
// refs cleared.
|
||||
m_memberObjects.clear();
|
||||
m_contentModels.clear();
|
||||
m_reactionModels.clear();
|
||||
m_readMarkerModels.clear();
|
||||
|
||||
beginResetModel();
|
||||
m_currentRoom = room;
|
||||
@@ -412,7 +409,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();
|
||||
static const KFormat format;
|
||||
const KFormat format;
|
||||
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
||||
}
|
||||
case SpecialMarksRole:
|
||||
@@ -434,6 +431,11 @@ 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);
|
||||
}
|
||||
|
||||
@@ -451,7 +453,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == GenericDisplayRole) {
|
||||
return EventHandler::genericBody(m_currentRoom, &evt);
|
||||
return EventHandler::genericBody(&evt);
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
@@ -640,7 +642,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, eventId));
|
||||
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, event));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <KFormat>
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
@@ -117,6 +118,7 @@ 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,11 +133,14 @@ bool MessageFilterModel::showAuthor(QModelIndex index) const
|
||||
|
||||
QString MessageFilterModel::aggregateEventToString(int sourceRow) const
|
||||
{
|
||||
QString aggregateString;
|
||||
QStringList parts;
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
aggregateString += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||
aggregateString += ", "_ls;
|
||||
parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||
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
|
||||
@@ -145,12 +148,46 @@ 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());
|
||||
|
||||
aggregateString = aggregateString.trimmed();
|
||||
if (aggregateString.endsWith(u',')) {
|
||||
aggregateString.removeLast();
|
||||
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 {};
|
||||
}
|
||||
return aggregateString;
|
||||
}
|
||||
|
||||
QVariantList MessageFilterModel::stateEventsList(int sourceRow) const
|
||||
|
||||
@@ -2,40 +2,20 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "permissionsmodel.h"
|
||||
#include "powerlevel.h"
|
||||
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
|
||||
#include <KLazyLocalizedString>
|
||||
|
||||
#include "powerlevel.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto UsersDefaultKey = "users_default"_L1;
|
||||
constexpr auto StateDefaultKey = "state_default"_L1;
|
||||
constexpr auto EventsDefaultKey = "events_default"_L1;
|
||||
|
||||
constexpr auto InviteKey = "invite"_L1;
|
||||
constexpr auto KickKey = "kick"_L1;
|
||||
constexpr auto BanKey = "ban"_L1;
|
||||
constexpr auto RedactKey = "redact"_L1;
|
||||
|
||||
static const QStringList defaultPermissions = {
|
||||
UsersDefaultKey,
|
||||
StateDefaultKey,
|
||||
EventsDefaultKey,
|
||||
};
|
||||
|
||||
static const QStringList basicPermissions = {
|
||||
InviteKey,
|
||||
KickKey,
|
||||
BanKey,
|
||||
RedactKey,
|
||||
};
|
||||
|
||||
static const QStringList knownPermissions = {
|
||||
QStringLiteral("users_default"),
|
||||
QStringLiteral("state_default"),
|
||||
QStringLiteral("events_default"),
|
||||
QStringLiteral("invite"),
|
||||
QStringLiteral("kick"),
|
||||
QStringLiteral("ban"),
|
||||
QStringLiteral("redact"),
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
QStringLiteral("m.room.power_levels"),
|
||||
@@ -53,14 +33,14 @@ static const QStringList knownPermissions = {
|
||||
};
|
||||
|
||||
// Alternate name text for default permissions.
|
||||
static const QHash<QString, KLazyLocalizedString> permissionNames = {
|
||||
{UsersDefaultKey, kli18nc("Room permission type", "Default user power level")},
|
||||
{StateDefaultKey, kli18nc("Room permission type", "Default power level to set the room state")},
|
||||
{EventsDefaultKey, kli18nc("Room permission type", "Default power level to send messages")},
|
||||
{InviteKey, kli18nc("Room permission type", "Invite users")},
|
||||
{KickKey, kli18nc("Room permission type", "Kick users")},
|
||||
{BanKey, kli18nc("Room permission type", "Ban users")},
|
||||
{RedactKey, kli18nc("Room permission type", "Remove messages sent by other users")},
|
||||
static const QHash<QString, KLazyLocalizedString> defaultPermissionNames = {
|
||||
{QStringLiteral("users_default"), kli18nc("Room permission type", "Default user power level")},
|
||||
{QStringLiteral("state_default"), kli18nc("Room permission type", "Default power level to set the room state")},
|
||||
{QStringLiteral("events_default"), kli18nc("Room permission type", "Default power level to send messages")},
|
||||
{QStringLiteral("invite"), kli18nc("Room permission type", "Invite users")},
|
||||
{QStringLiteral("kick"), kli18nc("Room permission type", "Kick users")},
|
||||
{QStringLiteral("ban"), kli18nc("Room permission type", "Ban users")},
|
||||
{QStringLiteral("redact"), kli18nc("Room permission type", "Remove messages sent by other users")},
|
||||
{QStringLiteral("m.reaction"), kli18nc("Room permission type", "Send reactions")},
|
||||
{QStringLiteral("m.room.redaction"), kli18nc("Room permission type", "Remove their own messages")},
|
||||
{QStringLiteral("m.room.power_levels"), kli18nc("Room permission type", "Change user permissions")},
|
||||
@@ -78,10 +58,10 @@ static const QHash<QString, KLazyLocalizedString> permissionNames = {
|
||||
};
|
||||
|
||||
// Subtitles for the default values.
|
||||
static const QHash<QString, KLazyLocalizedString> permissionSubtitles = {
|
||||
{UsersDefaultKey, kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
|
||||
{StateDefaultKey, kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
|
||||
{EventsDefaultKey, kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
|
||||
static const QHash<QString, KLazyLocalizedString> defaultSubtitles = {
|
||||
{QStringLiteral("users_default"), kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
|
||||
{QStringLiteral("state_default"), kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
|
||||
{QStringLiteral("events_default"), kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
|
||||
};
|
||||
|
||||
// Permissions that should use the event default.
|
||||
@@ -90,7 +70,6 @@ static const QStringList eventPermissions = {
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
};
|
||||
};
|
||||
|
||||
PermissionsModel::PermissionsModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
@@ -130,8 +109,6 @@ void PermissionsModel::initializeModel()
|
||||
}
|
||||
|
||||
m_permissions.append(defaultPermissions);
|
||||
m_permissions.append(basicPermissions);
|
||||
m_permissions.append(knownPermissions);
|
||||
|
||||
for (const auto &event : currentPowerLevelEvent->events().keys()) {
|
||||
if (!m_permissions.contains(event)) {
|
||||
@@ -154,17 +131,17 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
const auto permission = m_permissions.value(index.row());
|
||||
if (role == NameRole) {
|
||||
if (permissionNames.keys().contains(permission)) {
|
||||
return permissionNames.value(permission).toString();
|
||||
if (defaultPermissionNames.keys().contains(permission)) {
|
||||
return defaultPermissionNames.value(permission).toString();
|
||||
}
|
||||
return permission;
|
||||
}
|
||||
if (role == SubtitleRole) {
|
||||
if (knownPermissions.contains(permission) && permissionNames.keys().contains(permission)) {
|
||||
if (permission.startsWith(QLatin1String("m.")) && defaultPermissionNames.keys().contains(permission)) {
|
||||
return permission;
|
||||
}
|
||||
if (permissionSubtitles.contains(permission)) {
|
||||
return permissionSubtitles.value(permission).toString();
|
||||
if (defaultSubtitles.contains(permission)) {
|
||||
return defaultSubtitles.value(permission).toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
@@ -189,10 +166,11 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
return QString();
|
||||
}
|
||||
if (role == IsDefaultValueRole) {
|
||||
return defaultPermissions.contains(permission);
|
||||
return permission.contains(QLatin1String("default"));
|
||||
}
|
||||
if (role == IsBasicPermissionRole) {
|
||||
return basicPermissions.contains(permission);
|
||||
return permission == QStringLiteral("invite") || permission == QStringLiteral("kick") || permission == QStringLiteral("ban")
|
||||
|| permission == QStringLiteral("redact");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -223,19 +201,19 @@ std::optional<int> PermissionsModel::powerLevel(const QString &permission) const
|
||||
}
|
||||
|
||||
if (const auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>()) {
|
||||
if (permission == BanKey) {
|
||||
if (permission == QStringLiteral("ban")) {
|
||||
return currentPowerLevelEvent->ban();
|
||||
} else if (permission == KickKey) {
|
||||
} else if (permission == QStringLiteral("kick")) {
|
||||
return currentPowerLevelEvent->kick();
|
||||
} else if (permission == InviteKey) {
|
||||
} else if (permission == QStringLiteral("invite")) {
|
||||
return currentPowerLevelEvent->invite();
|
||||
} else if (permission == RedactKey) {
|
||||
} else if (permission == QStringLiteral("redact")) {
|
||||
return currentPowerLevelEvent->redact();
|
||||
} else if (permission == UsersDefaultKey) {
|
||||
} else if (permission == QStringLiteral("users_default")) {
|
||||
return currentPowerLevelEvent->usersDefault();
|
||||
} else if (permission == StateDefaultKey) {
|
||||
} else if (permission == QStringLiteral("state_default")) {
|
||||
return currentPowerLevelEvent->stateDefault();
|
||||
} else if (permission == EventsDefaultKey) {
|
||||
} else if (permission == QStringLiteral("events_default")) {
|
||||
return currentPowerLevelEvent->eventsDefault();
|
||||
} else if (eventPermissions.contains(permission)) {
|
||||
return currentPowerLevelEvent->powerLevelForEvent(permission);
|
||||
@@ -263,16 +241,13 @@ void PermissionsModel::setPowerLevel(const QString &permission, const int &newPo
|
||||
auto powerLevelContent = currentPowerLevelEvent->contentJson();
|
||||
if (powerLevelContent.contains(permission)) {
|
||||
powerLevelContent[permission] = clampPowerLevel;
|
||||
// Deal with the case where a default or basic permission is missing from the event content erroneously.
|
||||
} else if (defaultPermissions.contains(permission) || basicPermissions.contains(permission)) {
|
||||
powerLevelContent[permission] = clampPowerLevel;
|
||||
} else {
|
||||
auto eventPowerLevels = powerLevelContent[QLatin1String("events")].toObject();
|
||||
eventPowerLevels[permission] = clampPowerLevel;
|
||||
powerLevelContent[QLatin1String("events")] = eventPowerLevels;
|
||||
}
|
||||
|
||||
m_room->setState<Quotient::RoomPowerLevelsEvent>(Quotient::fromJson<Quotient::PowerLevelsEventContent>(powerLevelContent));
|
||||
m_room->setState<Quotient::RoomPowerLevelsEvent>(powerLevelContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -311,12 +311,8 @@ 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(),
|
||||
@@ -341,11 +337,7 @@ 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();
|
||||
});
|
||||
@@ -353,18 +345,10 @@ 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
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -378,11 +362,7 @@ 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)
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "reactionmodel.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFont>
|
||||
#ifdef HAVE_ICU
|
||||
#include <QTextBoundaryFinder>
|
||||
#include <QTextCharFormat>
|
||||
#include <unicode/uchar.h>
|
||||
#include <unicode/urename.h>
|
||||
#endif
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
@@ -149,6 +154,30 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
|
||||
};
|
||||
}
|
||||
|
||||
bool isEmoji(const QString &text)
|
||||
{
|
||||
#ifdef HAVE_ICU
|
||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||
int from = 0;
|
||||
while (finder.toNextBoundary() != -1) {
|
||||
auto to = finder.position();
|
||||
if (text[from].isSpace()) {
|
||||
from = to;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto first = text.mid(from, to - from).toUcs4()[0];
|
||||
if (!u_hasBinaryProperty(first, UCHAR_EMOJI)) {
|
||||
return false;
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString ReactionModel::reactionText(QString text) const
|
||||
{
|
||||
text = text.toHtmlEscaped();
|
||||
@@ -162,7 +191,7 @@ QString ReactionModel::reactionText(QString text) const
|
||||
.arg(m_room->connection()->makeMediaUrl(QUrl(text)).toString(), QString::number(size));
|
||||
}
|
||||
|
||||
return Utils::isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
return isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
}
|
||||
|
||||
#include "moc_reactionmodel.cpp"
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "enums/neochatroomtype.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
namespace Quotient
|
||||
|
||||
@@ -95,6 +95,7 @@ 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.id()));
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, &event));
|
||||
}
|
||||
if (event.isStateEvent()) {
|
||||
if (event.matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, event.id()));
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, &event));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -38,9 +38,6 @@ 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->id()));
|
||||
m_contentModels.push_back(new MessageContentModel(room, event.get()));
|
||||
}
|
||||
|
||||
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->id()));
|
||||
m_contentModels.push_front(new MessageContentModel(room, event));
|
||||
}
|
||||
|
||||
void ThreadModel::addModels()
|
||||
|
||||
@@ -94,7 +94,7 @@ int TimelineBeginningModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
if (m_room == nullptr) {
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
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 1;
|
||||
return 0;
|
||||
}
|
||||
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_allowEmpty && m_filterText.length() < 1) {
|
||||
if (m_filterText.length() < 1) {
|
||||
return false;
|
||||
}
|
||||
return sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
@@ -27,15 +27,4 @@ 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,7 +24,6 @@ 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:
|
||||
/**
|
||||
@@ -37,14 +36,9 @@ 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,11 +87,7 @@ 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,8 +263,6 @@ 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
|
||||
@@ -293,8 +291,6 @@ 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
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
#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>
|
||||
@@ -65,6 +68,10 @@ 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);
|
||||
@@ -74,9 +81,9 @@ void NeoChatConnection::connectSignals()
|
||||
Q_EMIT userConsentRequired(job->errorUrl());
|
||||
}
|
||||
});
|
||||
connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
|
||||
connect(this, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
|
||||
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
|
||||
Q_EMIT showMessage(MessageType::Warning, i18n("File too large to download.<br />Contact your matrix server administrator for support."));
|
||||
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
|
||||
}
|
||||
});
|
||||
connect(this, &NeoChatConnection::directChatsListChanged, this, [this](DirectChatsMap additions, DirectChatsMap removals) {
|
||||
@@ -109,10 +116,6 @@ 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()) {
|
||||
@@ -341,9 +344,17 @@ void NeoChatConnection::createRoom(const QString &name, const QString &topic, co
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT errorOccured(i18n("Room creation failed: %1", job->errorString()));
|
||||
connect(job, &CreateRoomJob::failure, this, [job] {
|
||||
Q_EMIT Controller::instance().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)
|
||||
@@ -370,9 +381,17 @@ void NeoChatConnection::createSpace(const QString &name, const QString &topic, c
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT errorOccured(i18n("Space creation failed: %1", job->errorString()));
|
||||
connect(job, &CreateRoomJob::failure, this, [job] {
|
||||
Q_EMIT Controller::instance().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)
|
||||
@@ -380,6 +399,29 @@ 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;
|
||||
@@ -476,7 +518,6 @@ 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,7 +14,6 @@
|
||||
#include <Quotient/keyimport.h>
|
||||
#endif
|
||||
|
||||
#include "enums/messagetype.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "models/threepidmodel.h"
|
||||
|
||||
@@ -155,6 +154,20 @@ 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.
|
||||
*/
|
||||
@@ -199,21 +212,6 @@ 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,7 +9,6 @@
|
||||
#include <QMimeDatabase>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#include <Quotient/events/eventcontent.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <qcoro/qcorosignal.h>
|
||||
@@ -37,15 +36,19 @@
|
||||
|
||||
#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>
|
||||
@@ -75,14 +78,10 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
QString mxcUrl;
|
||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||
if (event->hasFileContent()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mxcUrl = event->fileContent()->url().toString();
|
||||
#else
|
||||
mxcUrl = event->content()->fileInfo()->url().toString();
|
||||
#endif
|
||||
}
|
||||
} else if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
|
||||
mxcUrl = event->image().url().toString();
|
||||
mxcUrl = event->image().fileInfo()->url().toString();
|
||||
}
|
||||
if (mxcUrl.isEmpty()) {
|
||||
return;
|
||||
@@ -128,8 +127,41 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
updatePushNotificationState(QStringLiteral("m.push_rules"));
|
||||
|
||||
Q_EMIT canEncryptRoomChanged();
|
||||
if (this->joinState() == JoinState::Invite) {
|
||||
Q_EMIT showInviteNotification(this);
|
||||
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, roomMemberEvent, 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();
|
||||
}
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
@@ -220,11 +252,7 @@ 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::Base *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());
|
||||
@@ -239,12 +267,7 @@ 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::FileContent>(static_cast<EventContent::FileContent *>(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) {
|
||||
@@ -377,11 +400,7 @@ bool NeoChatRoom::lastEventIsSpoiler() const
|
||||
if (auto event = lastEvent()) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (e->hasTextContent() && e->content() && e->mimeType().name() == "text/html"_ls) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto htmlBody = static_cast<const Quotient::EventContent::TextContent *>(e->content().get())->body;
|
||||
#else
|
||||
auto htmlBody = static_cast<const Quotient::EventContent::TextContent *>(e->content())->body;
|
||||
#endif
|
||||
return htmlBody.contains("data-mx-spoiler"_ls);
|
||||
}
|
||||
}
|
||||
@@ -925,6 +944,11 @@ 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;
|
||||
@@ -1216,11 +1240,7 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
for (const auto &i : roomRuleArray) {
|
||||
QJsonObject roomRule = i.toObject();
|
||||
if (roomRule["rule_id"_ls] == id()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection()->callApi<DeletePushRuleJob>("room"_ls, id());
|
||||
#else
|
||||
connection()->callApi<DeletePushRuleJob>(QLatin1String("global"), "room"_ls, id());
|
||||
#endif
|
||||
connection()->callApi<DeletePushRuleJob>("global"_ls, "room"_ls, id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1231,11 +1251,7 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1271,17 +1287,9 @@ 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;
|
||||
});
|
||||
@@ -1305,17 +1313,9 @@ 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,17 +1344,9 @@ 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;
|
||||
});
|
||||
@@ -1423,9 +1415,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, [this, job]() {
|
||||
connect(job, &BaseJob::finished, this, [job]() {
|
||||
if (job->error() == BaseJob::Success) {
|
||||
Q_EMIT showMessage(MessageType::Positive, i18n("Report sent successfully."));
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18n("Report sent successfully."));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1512,18 +1504,12 @@ FileTransferInfo NeoChatRoom::cachedFileTransferInfo(const Quotient::RoomEvent *
|
||||
int total = 0;
|
||||
if (auto evt = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
if (evt->hasFileContent()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const auto fileContent = evt->fileContent();
|
||||
#else
|
||||
const auto fileContent = evt->content()->fileInfo();
|
||||
#endif
|
||||
|
||||
mxcUrl = fileContent->url().toString();
|
||||
total = fileContent->payloadSize;
|
||||
mxcUrl = evt->content()->fileInfo()->url().toString();
|
||||
total = evt->content()->fileInfo()->payloadSize;
|
||||
}
|
||||
} else if (auto evt = eventCast<const Quotient::StickerEvent>(event)) {
|
||||
mxcUrl = evt->image().url().toString();
|
||||
total = evt->image().payloadSize;
|
||||
mxcUrl = evt->image().fileInfo()->url().toString();
|
||||
total = evt->image().fileInfo()->payloadSize;
|
||||
}
|
||||
|
||||
FileTransferInfo transferInfo = fileTransferInfo(event->id());
|
||||
@@ -1741,13 +1727,9 @@ int NeoChatRoom::maxRoomVersion() const
|
||||
return maxVersion;
|
||||
}
|
||||
|
||||
NeochatRoomMember *NeoChatRoom::directChatRemoteMember()
|
||||
Quotient::RoomMember NeoChatRoom::directChatRemoteMember() const
|
||||
{
|
||||
if (directChatMembers().size() == 0) {
|
||||
qWarning() << "No other member available in this room";
|
||||
return {};
|
||||
}
|
||||
return new NeochatRoomMember(this, directChatMembers()[0].id());
|
||||
return directChatMembers()[0];
|
||||
}
|
||||
|
||||
void NeoChatRoom::sendLocation(float lat, float lon, const QString &description)
|
||||
@@ -1782,9 +1764,6 @@ 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,9 +12,7 @@
|
||||
#include <QCoroTask>
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
#include "enums/messagetype.h"
|
||||
#include "enums/pushrule.h"
|
||||
#include "neochatroommember.h"
|
||||
#include "pollhandler.h"
|
||||
|
||||
namespace Quotient
|
||||
@@ -76,7 +74,7 @@ class NeoChatRoom : public Quotient::Room
|
||||
/**
|
||||
* @brief Get a RoomMember object for the other person in a direct chat.
|
||||
*/
|
||||
Q_PROPERTY(NeochatRoomMember *directChatRemoteMember READ directChatRemoteMember CONSTANT)
|
||||
Q_PROPERTY(Quotient::RoomMember directChatRemoteMember READ directChatRemoteMember CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The Matrix IDs of this room's parents.
|
||||
@@ -326,7 +324,7 @@ public:
|
||||
|
||||
[[nodiscard]] QString avatarMediaId() const;
|
||||
|
||||
NeochatRoomMember *directChatRemoteMember();
|
||||
Quotient::RoomMember directChatRemoteMember() const;
|
||||
|
||||
/**
|
||||
* @brief Whether this room has one or more parent spaces set.
|
||||
@@ -418,6 +416,8 @@ public:
|
||||
|
||||
bool readOnly() const;
|
||||
|
||||
Q_INVOKABLE void clearInvitationNotification();
|
||||
|
||||
[[nodiscard]] QString joinRule() const;
|
||||
|
||||
/**
|
||||
@@ -657,19 +657,6 @@ 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.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user