Compare commits
95 Commits
v23.04.2
...
work/yeetk
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce3915e22a | ||
|
|
741f6ea659 | ||
|
|
310e9b7ba3 | ||
|
|
57b6f00d8e | ||
|
|
280c9327cb | ||
|
|
05bcbb695f | ||
|
|
552f4e8f13 | ||
|
|
1223c5348d | ||
|
|
3ccff4f337 | ||
|
|
a67f3334ea | ||
|
|
52dafbb6c8 | ||
|
|
4341cc437d | ||
|
|
7bb7dd7bbb | ||
|
|
b4090d9671 | ||
|
|
3d2bcce99a | ||
|
|
f3a04635cf | ||
|
|
419cb07557 | ||
|
|
57fccaa076 | ||
|
|
4bf65339f8 | ||
|
|
e5f2e209a2 | ||
|
|
be3b5cdb8a | ||
|
|
abd56baa51 | ||
|
|
ff27a1d0bb | ||
|
|
8af2d4d273 | ||
|
|
f64c8e28da | ||
|
|
4570d6350b | ||
|
|
6dd51a35c5 | ||
|
|
2470990d75 | ||
|
|
1a87e605d6 | ||
|
|
e995740790 | ||
|
|
0fb8b740a4 | ||
|
|
5087161e4b | ||
|
|
918e9e492a | ||
|
|
f10805dddb | ||
|
|
55e4d03dfe | ||
|
|
9f9086b4b0 | ||
|
|
e00ce79d26 | ||
|
|
a13b2e6bd2 | ||
|
|
cefe5acdaa | ||
|
|
1438aea965 | ||
|
|
1b60e24c64 | ||
|
|
4f1c8f6f35 | ||
|
|
882945260a | ||
|
|
3677088104 | ||
|
|
8f141cd88d | ||
|
|
0d1b35b610 | ||
|
|
87c20bf03c | ||
|
|
77479ca22d | ||
|
|
34ad743e98 | ||
|
|
493e27622f | ||
|
|
b1b6c7ceed | ||
|
|
d0b1610a9f | ||
|
|
dcf520a7a9 | ||
|
|
a0ae8b28b2 | ||
|
|
78a6179a11 | ||
|
|
9c91557d8f | ||
|
|
fb24ffd20d | ||
|
|
0c985a0af1 | ||
|
|
787dc5ab66 | ||
|
|
f11abdeebd | ||
|
|
89141dddf2 | ||
|
|
69f08de0ff | ||
|
|
925e20ebb8 | ||
|
|
ee254a286d | ||
|
|
3cc8d32dd3 | ||
|
|
50cf6d9750 | ||
|
|
cedbb64932 | ||
|
|
b45898a5b6 | ||
|
|
ccb1748ab3 | ||
|
|
11e9af15f7 | ||
|
|
8ad41fec92 | ||
|
|
ac5212ebb2 | ||
|
|
4e16b91f54 | ||
|
|
343f4965ed | ||
|
|
ac775c5aaf | ||
|
|
db17888d42 | ||
|
|
f12d10baa6 | ||
|
|
4289c1345f | ||
|
|
0d6a83b063 | ||
|
|
76b04dcba9 | ||
|
|
64dee7eb12 | ||
|
|
bc84ad8d56 | ||
|
|
afe8a2a5e4 | ||
|
|
87213dc9dd | ||
|
|
300d2428eb | ||
|
|
abe7d70822 | ||
|
|
d8bf26158a | ||
|
|
741cb57105 | ||
|
|
81c73037ca | ||
|
|
f6ba4f2ecd | ||
|
|
23303c0483 | ||
|
|
5a02448326 | ||
|
|
411ae25e80 | ||
|
|
6e1b3fe860 | ||
|
|
4a38d83a68 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ kate.project.ctags.*
|
|||||||
*.user
|
*.user
|
||||||
.flatpak-builder/
|
.flatpak-builder/
|
||||||
.idea/
|
.idea/
|
||||||
|
cmake-build-*
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
include:
|
include:
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||||
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Dependencies:
|
|||||||
'frameworks/kconfig': '@stable'
|
'frameworks/kconfig': '@stable'
|
||||||
'frameworks/syntax-highlighting': '@stable'
|
'frameworks/syntax-highlighting': '@stable'
|
||||||
'frameworks/kitemmodels': '@stable'
|
'frameworks/kitemmodels': '@stable'
|
||||||
|
'frameworks/kquickcharts': '@stable'
|
||||||
'frameworks/knotifications': '@stable'
|
'frameworks/knotifications': '@stable'
|
||||||
'libraries/kquickimageeditor': '@stable'
|
'libraries/kquickimageeditor': '@stable'
|
||||||
'frameworks/sonnet': '@stable'
|
'frameworks/sonnet': '@stable'
|
||||||
@@ -38,6 +39,7 @@ Dependencies:
|
|||||||
'frameworks/kconfig': '@latest-kf6'
|
'frameworks/kconfig': '@latest-kf6'
|
||||||
'frameworks/syntax-highlighting': '@latest-kf6'
|
'frameworks/syntax-highlighting': '@latest-kf6'
|
||||||
'frameworks/kitemmodels': '@latest-kf6'
|
'frameworks/kitemmodels': '@latest-kf6'
|
||||||
|
'frameworks/kquickcharts': '@latest-kf6'
|
||||||
'frameworks/knotifications': '@latest-kf6'
|
'frameworks/knotifications': '@latest-kf6'
|
||||||
'libraries/kquickimageeditor': '@latest-kf6'
|
'libraries/kquickimageeditor': '@latest-kf6'
|
||||||
'frameworks/sonnet': '@latest-kf6'
|
'frameworks/sonnet': '@latest-kf6'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
|
|
||||||
# KDE Applications version, managed by release script.
|
# KDE Applications version, managed by release script.
|
||||||
set(RELEASE_SERVICE_VERSION_MAJOR "23")
|
set(RELEASE_SERVICE_VERSION_MAJOR "23")
|
||||||
set(RELEASE_SERVICE_VERSION_MINOR "03")
|
set(RELEASE_SERVICE_VERSION_MINOR "07")
|
||||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||||
|
|
||||||
@@ -68,12 +68,6 @@ set_package_properties(KF${QT_MAJOR_VERSION}Kirigami2 PROPERTIES
|
|||||||
)
|
)
|
||||||
find_package(KF${QT_MAJOR_VERSION}KirigamiAddons 0.7.2 REQUIRED)
|
find_package(KF${QT_MAJOR_VERSION}KirigamiAddons 0.7.2 REQUIRED)
|
||||||
|
|
||||||
find_package(Qt${QT_MAJOR_VERSION}Keychain)
|
|
||||||
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
|
|
||||||
TYPE REQUIRED
|
|
||||||
PURPOSE "Secure storage of account secrets"
|
|
||||||
)
|
|
||||||
|
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
find_package(OpenSSL)
|
find_package(OpenSSL)
|
||||||
set_package_properties(OpenSSL PROPERTIES
|
set_package_properties(OpenSSL PROPERTIES
|
||||||
@@ -93,7 +87,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
|||||||
find_package(KF${QT_MAJOR_VERSION}DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
find_package(KF${QT_MAJOR_VERSION}DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Quotient 0.6)
|
find_package(Quotient 0.7)
|
||||||
set_package_properties(Quotient PROPERTIES
|
set_package_properties(Quotient PROPERTIES
|
||||||
TYPE REQUIRED
|
TYPE REQUIRED
|
||||||
DESCRIPTION "Qt wrapper around Matrix API"
|
DESCRIPTION "Qt wrapper around Matrix API"
|
||||||
@@ -111,6 +105,7 @@ set_package_properties(cmark PROPERTIES
|
|||||||
|
|
||||||
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
|
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
|
||||||
ecm_find_qmlmodule(org.kde.kitemmodels 1.0)
|
ecm_find_qmlmodule(org.kde.kitemmodels 1.0)
|
||||||
|
ecm_find_qmlmodule(org.kde.quickcharts 1.0)
|
||||||
|
|
||||||
find_package(KQuickImageEditor COMPONENTS)
|
find_package(KQuickImageEditor COMPONENTS)
|
||||||
set_package_properties(KQuickImageEditor PROPERTIES
|
set_package_properties(KQuickImageEditor PROPERTIES
|
||||||
@@ -130,13 +125,9 @@ set_package_properties(KF${QT_MAJOR_VERSION}DocTools PROPERTIES DESCRIPTION
|
|||||||
TYPE OPTIONAL
|
TYPE OPTIONAL
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(Sqlite3)
|
|
||||||
|
|
||||||
if(NOT Quotient_VERSION_MINOR GREATER 6)
|
|
||||||
cmake_policy(SET CMP0063 OLD)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
|
find_package(Sqlite3)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -150,7 +141,7 @@ install(FILES org.kde.neochat.tray.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/h
|
|||||||
add_definitions(-DQT_NO_FOREACH)
|
add_definitions(-DQT_NO_FOREACH)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
if (BUILD_TESTING AND Quotient_VERSION_MINOR GREATER 6)
|
if (BUILD_TESTING)
|
||||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
|
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
|
||||||
add_subdirectory(autotests)
|
add_subdirectory(autotests)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -8,3 +8,9 @@ ecm_add_test(
|
|||||||
LINK_LIBRARIES neochat Qt::Test Quotient
|
LINK_LIBRARIES neochat Qt::Test Quotient
|
||||||
TEST_NAME neochatroomtest
|
TEST_NAME neochatroomtest
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ecm_add_test(
|
||||||
|
texthandlertest.cpp
|
||||||
|
LINK_LIBRARIES neochat Qt::Test
|
||||||
|
TEST_NAME texthandlertest
|
||||||
|
)
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ void NeoChatRoomTest::initTestCase()
|
|||||||
void NeoChatRoomTest::subtitleTextTest()
|
void NeoChatRoomTest::subtitleTextTest()
|
||||||
{
|
{
|
||||||
QCOMPARE(room->timelineSize(), 1);
|
QCOMPARE(room->timelineSize(), 1);
|
||||||
QCOMPARE(room->subtitleText(), QStringLiteral("@example:example.org: This is an example text message"));
|
QCOMPARE(room->lastEventToString(), QStringLiteral("@example:example.org: This is an example text message"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoomTest::eventTest()
|
void NeoChatRoomTest::eventTest()
|
||||||
|
|||||||
587
autotests/texthandlertest.cpp
Normal file
587
autotests/texthandlertest.cpp
Normal file
@@ -0,0 +1,587 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
#include "texthandler.h"
|
||||||
|
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <quotient_common.h>
|
||||||
|
#include <syncdata.h>
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
class TestRoom : public NeoChatRoom
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using NeoChatRoom::NeoChatRoom;
|
||||||
|
|
||||||
|
void update(SyncRoomData &&data, bool fromCache = false)
|
||||||
|
{
|
||||||
|
Room::updateData(std::move(data), fromCache);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextHandlerTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
Connection *connection = nullptr;
|
||||||
|
TestRoom *room = nullptr;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void initTestCase();
|
||||||
|
|
||||||
|
void allowedAttributes();
|
||||||
|
void stripDisallowedTags();
|
||||||
|
void stripDisallowedAttributes();
|
||||||
|
void emptyCodeTags();
|
||||||
|
|
||||||
|
void sendSimpleStringCase();
|
||||||
|
void sendSingleParaMarkup();
|
||||||
|
void sendMultipleSectionMarkup();
|
||||||
|
void sendBadLinks();
|
||||||
|
void sendEscapeCode();
|
||||||
|
void sendCodeClass();
|
||||||
|
|
||||||
|
void receiveStripReply();
|
||||||
|
void receivePlainTextIn();
|
||||||
|
|
||||||
|
void receiveRichInPlainOut_data();
|
||||||
|
void receiveRichInPlainOut();
|
||||||
|
void receivePlainStripHtml();
|
||||||
|
void receivePlainStripMarkup();
|
||||||
|
void receiveStripNewlines();
|
||||||
|
|
||||||
|
void receiveRichUserPill();
|
||||||
|
void receiveRichStrikethrough();
|
||||||
|
void receiveRichtextIn();
|
||||||
|
void receiveRichMxcUrl();
|
||||||
|
void receiveRichPlainUrl();
|
||||||
|
void receiveRichEmote();
|
||||||
|
void receiveRichEdited_data();
|
||||||
|
void receiveRichEdited();
|
||||||
|
};
|
||||||
|
|
||||||
|
void TextHandlerTest::initTestCase()
|
||||||
|
{
|
||||||
|
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||||
|
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
|
||||||
|
|
||||||
|
const auto json = QJsonDocument::fromJson(R"EVENT({
|
||||||
|
"account_data": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"tags": {
|
||||||
|
"u.work": {
|
||||||
|
"order": 0.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"custom_config_key": "custom_config_value"
|
||||||
|
},
|
||||||
|
"type": "org.example.custom.room.config"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ephemeral": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"user_ids": [
|
||||||
|
"@alice:matrix.org",
|
||||||
|
"@bob:example.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"type": "m.typing"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||||
|
"displayname": "Alice Margatroid",
|
||||||
|
"membership": "join",
|
||||||
|
"reason": "Looking for support"
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"state_key": "@alice:example.org",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"m.heroes": [
|
||||||
|
"@alice:example.com",
|
||||||
|
"@bob:example.com"
|
||||||
|
],
|
||||||
|
"m.invited_member_count": 0,
|
||||||
|
"m.joined_member_count": 2
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "This is an **example** text message",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<b>This is an example text message</b>",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1232
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "/me This is an emote.",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "This is an emote.",
|
||||||
|
"msgtype": "m.emote"
|
||||||
|
},
|
||||||
|
"event_id": "$153273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 1532735824654,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1231
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "tested",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$zrCiBxBnqqTn0Z5FY78qSZAszno_w8nJJXzfBULG-3E",
|
||||||
|
"origin_server_ts": 1680948575928,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1747776,
|
||||||
|
"m.relations": {
|
||||||
|
"m.replace": {
|
||||||
|
"event_id": "$UX0PlpyI7vYO32iHMuuYEP7ECMh4sX3XLGiB2SwM4mQ",
|
||||||
|
"origin_server_ts": 1680948580992,
|
||||||
|
"sender": "@example:example.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limited": true,
|
||||||
|
"prev_batch": "t34-23535_0_0"
|
||||||
|
}
|
||||||
|
})EVENT");
|
||||||
|
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
|
||||||
|
room->update(std::move(roomData));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::allowedAttributes()
|
||||||
|
{
|
||||||
|
const QString testInputString1 = QStringLiteral("<p><span data-mx-spoiler><font color=#FFFFFF>Test</font><span></p>");
|
||||||
|
const QString testOutputString1 = QStringLiteral("<p><span data-mx-spoiler><font color=#FFFFFF>Test</font><span></p>");
|
||||||
|
// Handle urls where the href has either single (') or double (") quotes.
|
||||||
|
const QString testInputString2 = QStringLiteral("<p><a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a></p>");
|
||||||
|
const QString testOutputString2 = QStringLiteral("<p><a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a></p>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString1);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString1);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1);
|
||||||
|
|
||||||
|
testTextHandler.setData(testInputString2);
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString2);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::stripDisallowedTags()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("<p>Allowed</p> <span>Allowed</span> <body>Disallowed</body>");
|
||||||
|
const QString testOutputString = QStringLiteral("<p>Allowed</p> <span>Allowed</span> Disallowed");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::stripDisallowedAttributes()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("<p style=\"font-size:50px;\" color=#FFFFFF>Test</p>");
|
||||||
|
const QString testOutputString = QStringLiteral("<p>Test</p>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that empty code tags are handled.
|
||||||
|
* (this was a bug during development hence the test)
|
||||||
|
*/
|
||||||
|
void TextHandlerTest::emptyCodeTags()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("<pre><code></code></pre>");
|
||||||
|
const QString testOutputString = QStringLiteral("<pre><code></code></pre>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::sendSimpleStringCase()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("This data should just be put in a paragraph.");
|
||||||
|
const QString testOutputString = QStringLiteral("<p>This data should just be put in a paragraph.</p>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::sendSingleParaMarkup()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral(
|
||||||
|
"Text para with **bold**, *italic*, [link](https://kde.org), , `inline code`.");
|
||||||
|
const QString testOutputString = QStringLiteral(
|
||||||
|
"<p>Text para with <strong>bold</strong>, <em>italic</em>, <a href=\"https://kde.org\">link</a>, <img "
|
||||||
|
"src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\">, <code>inline code</code>.</p>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::sendMultipleSectionMarkup()
|
||||||
|
{
|
||||||
|
const QString testInputString =
|
||||||
|
QStringLiteral("Text para\n> blockquote\n* List 1\n* List 2\n1. one\n2. two\n# Heading 1\n## Heading 2\nhorizontal rule\n\n---\n```\ncodeblock\n```");
|
||||||
|
const QString testOutputString = QStringLiteral(
|
||||||
|
"<p>Text para</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>\n<ul>\n<li>List 1</li>\n<li>List "
|
||||||
|
"2</li>\n</ul>\n<ol>\n<li>one</li>\n<li>two</li>\n</ol>\n<h1>Heading 1</h1>\n<h2>Heading 2</h2>\n<p>horizontal "
|
||||||
|
"rule</p>\n<hr>\n<pre><code>codeblock\n</code></pre>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::sendBadLinks()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("[link](kde.org), ");
|
||||||
|
const QString testOutputString = QStringLiteral("<p><a>link</a>, <img alt=\"image\"></p>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All text between code tags is treated as plain so it should get escaped.
|
||||||
|
*/
|
||||||
|
void TextHandlerTest::sendEscapeCode()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("```\n<p>Test <span style=\"font-size:50px;\">some</span> code</p>\n```");
|
||||||
|
const QString testOutputString =
|
||||||
|
QStringLiteral("<pre><code><p>Test <span style="font-size:50px;">some</span> code</p>\n</code></pre>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::sendCodeClass()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("```html\nsome code\n```\n<pre><code class=\"code-underline\">some more code</code></pre>");
|
||||||
|
const QString testOutputString = QStringLiteral("<pre><code class=\"language-html\">some code\n</code></pre>\n<pre><code>some more code</code></pre>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveStripReply()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral(
|
||||||
|
"<mx-reply><blockquote><a href=\"https://matrix.to/#/!somewhere:example.org/$event:example.org\">In reply to</a><a "
|
||||||
|
"href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a><br />Message replied to.</blockquote></mx-reply>Reply message.");
|
||||||
|
const QString testOutputString = QStringLiteral("Reply message.");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichInPlainOut_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("testInputString");
|
||||||
|
QTest::addColumn<QString>("testOutputString");
|
||||||
|
|
||||||
|
QTest::newRow("ampersand") << QStringLiteral("a & b") << QStringLiteral("a & b");
|
||||||
|
QTest::newRow("quote") << QStringLiteral(""a and b"") << QStringLiteral("\"a and b\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichInPlainOut()
|
||||||
|
{
|
||||||
|
QFETCH(QString, testInputString);
|
||||||
|
QFETCH(QString, testOutputString);
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receivePlainTextIn()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("<plain text in tag bracket>\nTest link https://kde.org.");
|
||||||
|
const QString testOutputStringRich = QStringLiteral("<plain text in tag bracket><br>Test link <a href=\"https://kde.org\">https://kde.org</a>.");
|
||||||
|
QString testOutputStringPlain = QStringLiteral("<plain text in tag bracket>\nTest link https://kde.org.");
|
||||||
|
|
||||||
|
// Make sure quotes are maintained in a plain string.
|
||||||
|
const QString testInputString2 = QStringLiteral("last line is \"Time to switch to a new topic.\"");
|
||||||
|
const QString testOutputString2 = QStringLiteral("last line is \"Time to switch to a new topic.\"");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText), testOutputStringRich);
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputStringPlain);
|
||||||
|
|
||||||
|
testTextHandler.setData(testInputString2);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText), testOutputString2);
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveStripNewlines()
|
||||||
|
{
|
||||||
|
const QString testInputStringPlain = QStringLiteral("Test\nmany\nnew\nlines.");
|
||||||
|
const QString testInputStringRich = QStringLiteral("Test<br>many<br />new<br>lines.");
|
||||||
|
const QString testOutputString = QStringLiteral("Test many new lines.");
|
||||||
|
|
||||||
|
const QString testInputStringPlain2 = QStringLiteral("* List\n* Items");
|
||||||
|
const QString testOutputString2 = QStringLiteral("List Items");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputStringPlain);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::PlainText, true), testOutputString);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText, nullptr, nullptr, true), testOutputString);
|
||||||
|
|
||||||
|
testTextHandler.setData(testInputStringRich);
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText, true), testOutputString);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, nullptr, nullptr, true), testOutputString);
|
||||||
|
|
||||||
|
testTextHandler.setData(testInputStringPlain2);
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText, true), testOutputString2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a plain text output of a received string all html is stripped except for
|
||||||
|
* code which is unescaped if it's html.
|
||||||
|
*/
|
||||||
|
void TextHandlerTest::receivePlainStripHtml()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>");
|
||||||
|
const QString testOutputString = QStringLiteral("Test Some code <strong>with tags</strong>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receivePlainStripMarkup()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("**bold** `<p>inline code</p>` *italic*");
|
||||||
|
const QString testOutputString = QStringLiteral("bold <p>inline code</p> italic");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichUserPill()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>");
|
||||||
|
const QString testOutputString = QStringLiteral("<p><b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b></p>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichStrikethrough()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("<p><del>Test</del></p>");
|
||||||
|
const QString testOutputString = QStringLiteral("<p><s>Test</s></p>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichtextIn()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral("<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>");
|
||||||
|
const QString testOutputString = QStringLiteral("<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichMxcUrl()
|
||||||
|
{
|
||||||
|
const QString testInputString = QStringLiteral(
|
||||||
|
"<img src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\"><img src=\"mxc://kde.org/34c3464b3a1bd7f55af2d559e07d2c773c430e73\" "
|
||||||
|
"alt=\"image\">");
|
||||||
|
const QString testOutputString = QStringLiteral(
|
||||||
|
"<img "
|
||||||
|
"src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e?user_id=@bob:kde.org&room_id=%23myroom:kde.org&event_id=$143273582443PhrSn:example.org\" "
|
||||||
|
"alt=\"image\"><img "
|
||||||
|
"src=\"mxc://kde.org/34c3464b3a1bd7f55af2d559e07d2c773c430e73?user_id=@bob:kde.org&room_id=%23myroom:kde.org&event_id=$143273582443PhrSn:example.org\" "
|
||||||
|
"alt=\"image\">");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For when your rich input string has a plain text url left in.
|
||||||
|
*
|
||||||
|
* This test is to show that a url that is already rich will be left alone but a
|
||||||
|
* plain one will be linkified.
|
||||||
|
*/
|
||||||
|
void TextHandlerTest::receiveRichPlainUrl()
|
||||||
|
{
|
||||||
|
// This is an actual link that caused trouble which is why it's so long. Keeping
|
||||||
|
// so we can confirm consistent behaviour for complex urls.
|
||||||
|
const QString testInputStringLink1 = QStringLiteral(
|
||||||
|
"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im "
|
||||||
|
"<a "
|
||||||
|
"href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/"
|
||||||
|
"$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">Link already rich</a>");
|
||||||
|
const QString testOutputStringLink1 = QStringLiteral(
|
||||||
|
"<a "
|
||||||
|
"href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/"
|
||||||
|
"$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">https://matrix.to/#/"
|
||||||
|
"!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im</a> <a "
|
||||||
|
"href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/"
|
||||||
|
"$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">Link already rich</a>");
|
||||||
|
|
||||||
|
// Another real case. The linkification wasn't handling it when a single link
|
||||||
|
// contains what looks like and email. It was been broken into 3 but needs to
|
||||||
|
// be just single link.
|
||||||
|
const QString testInputStringLink2 = QStringLiteral("https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/");
|
||||||
|
const QString testOutputStringLink2 = QStringLiteral(
|
||||||
|
"<a "
|
||||||
|
"href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/"
|
||||||
|
"CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>");
|
||||||
|
|
||||||
|
QString testInputStringEmail = QStringLiteral(R"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)");
|
||||||
|
QString testOutputStringEmail =
|
||||||
|
QStringLiteral(R"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)");
|
||||||
|
|
||||||
|
QString testInputStringMxId = QStringLiteral("@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>");
|
||||||
|
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>");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputStringLink1);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink1);
|
||||||
|
|
||||||
|
testTextHandler.setData(testInputStringLink2);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink2);
|
||||||
|
|
||||||
|
testTextHandler.setData(testInputStringEmail);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringEmail);
|
||||||
|
|
||||||
|
testTextHandler.setData(testInputStringMxId);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that user pill is add to an emote message.
|
||||||
|
// N.B. The second message in the test timeline is marked as an emote.
|
||||||
|
void TextHandlerTest::receiveRichEmote()
|
||||||
|
{
|
||||||
|
auto event = room->messageEvents().at(1).get();
|
||||||
|
auto author = static_cast<NeoChatUser *>(room->user(event->senderId()));
|
||||||
|
const QString testInputString = QStringLiteral("This is an emote.");
|
||||||
|
const QString testOutputString = QStringLiteral("* <a href=\"https://matrix.to/#/@example:example.org\" style=\"color:") + author->color().name()
|
||||||
|
+ QStringLiteral("\">@example:example.org</a> This is an emote.");
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, event), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichEdited_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("testInputString");
|
||||||
|
QTest::addColumn<QString>("testOutputString");
|
||||||
|
|
||||||
|
QTest::newRow("basic") << QStringLiteral("Edited") << QStringLiteral("Edited <span style=\"color:#000000\">(edited)</span>");
|
||||||
|
QTest::newRow("multiple paragraphs") << QStringLiteral("<p>Edited</p>\n<p>Edited</p>")
|
||||||
|
<< QStringLiteral("<p>Edited</p>\n<p>Edited <span style=\"color:#000000\">(edited)</span></p>");
|
||||||
|
QTest::newRow("blockquote") << QStringLiteral("<blockquote>Edited</blockquote>")
|
||||||
|
<< QStringLiteral("<blockquote>Edited</blockquote><p> <span style=\"color:#000000\">(edited)</span></p>");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextHandlerTest::receiveRichEdited()
|
||||||
|
{
|
||||||
|
QFETCH(QString, testInputString);
|
||||||
|
QFETCH(QString, testOutputString);
|
||||||
|
|
||||||
|
TextHandler testTextHandler;
|
||||||
|
testTextHandler.setData(testInputString);
|
||||||
|
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(2).get()), testOutputString);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(TextHandlerTest)
|
||||||
|
#include "texthandlertest.moc"
|
||||||
1285
po/ar/neochat.po
1285
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1375
po/az/neochat.po
1375
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1267
po/ca/neochat.po
1267
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1258
po/cs/neochat.po
1258
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1273
po/da/neochat.po
1273
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1245
po/de/neochat.po
1245
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1241
po/el/neochat.po
1241
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1293
po/en_GB/neochat.po
1293
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1268
po/es/neochat.po
1268
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1120
po/eu/neochat.po
1120
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
2440
po/fi/neochat.po
2440
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1272
po/fr/neochat.po
1272
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1379
po/hu/neochat.po
1379
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1685
po/ia/neochat.po
1685
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1246
po/id/neochat.po
1246
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1301
po/ie/neochat.po
1301
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1304
po/it/neochat.po
1304
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1232
po/ja/neochat.po
1232
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1264
po/ka/neochat.po
1264
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1360
po/ko/neochat.po
1360
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1235
po/lt/neochat.po
1235
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1260
po/nl/neochat.po
1260
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
419
po/nn/neochat.po
419
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1363
po/pa/neochat.po
1363
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1353
po/pl/neochat.po
1353
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1259
po/pt/neochat.po
1259
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1375
po/pt_BR/neochat.po
1375
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1245
po/ru/neochat.po
1245
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1382
po/sk/neochat.po
1382
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1268
po/sl/neochat.po
1268
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1363
po/sv/neochat.po
1363
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1315
po/ta/neochat.po
1315
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1283
po/tok/neochat.po
1283
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1266
po/tr/neochat.po
1266
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1263
po/uk/neochat.po
1263
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1274
po/zh_CN/neochat.po
1274
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1231
po/zh_TW/neochat.po
1231
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -24,14 +24,12 @@ add_library(neochat STATIC
|
|||||||
models/publicroomlistmodel.cpp
|
models/publicroomlistmodel.cpp
|
||||||
models/userdirectorylistmodel.cpp
|
models/userdirectorylistmodel.cpp
|
||||||
models/keywordnotificationrulemodel.cpp
|
models/keywordnotificationrulemodel.cpp
|
||||||
utils.cpp
|
|
||||||
notificationsmanager.cpp
|
notificationsmanager.cpp
|
||||||
models/sortfilterroomlistmodel.cpp
|
models/sortfilterroomlistmodel.cpp
|
||||||
chatdocumenthandler.cpp
|
chatdocumenthandler.cpp
|
||||||
models/devicesmodel.cpp
|
models/devicesmodel.cpp
|
||||||
filetypesingleton.cpp
|
filetypesingleton.cpp
|
||||||
login.cpp
|
login.cpp
|
||||||
stickerevent.cpp
|
|
||||||
models/webshortcutmodel.cpp
|
models/webshortcutmodel.cpp
|
||||||
blurhash.cpp
|
blurhash.cpp
|
||||||
blurhashimageprovider.cpp
|
blurhashimageprovider.cpp
|
||||||
@@ -47,8 +45,13 @@ add_library(neochat STATIC
|
|||||||
models/statemodel.cpp
|
models/statemodel.cpp
|
||||||
filetransferpseudojob.cpp
|
filetransferpseudojob.cpp
|
||||||
models/searchmodel.cpp
|
models/searchmodel.cpp
|
||||||
|
texthandler.cpp
|
||||||
|
pollevent.cpp
|
||||||
|
pollhandler.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(neochat PUBLIC "-DQT_NO_KEYWORDS")
|
||||||
|
|
||||||
add_executable(neochat-app
|
add_executable(neochat-app
|
||||||
main.cpp
|
main.cpp
|
||||||
res.qrc
|
res.qrc
|
||||||
@@ -60,14 +63,6 @@ target_link_libraries(neochat-app PRIVATE
|
|||||||
neochat
|
neochat
|
||||||
)
|
)
|
||||||
|
|
||||||
if(Quotient_VERSION_MINOR GREATER 6)
|
|
||||||
target_compile_definitions(neochat PUBLIC QUOTIENT_07)
|
|
||||||
target_sources(neochat PRIVATE pollevent.cpp pollhandler.cpp)
|
|
||||||
else()
|
|
||||||
target_compile_definitions(neochat PUBLIC QUOTIENT_VERSION=\"${Quotient_VERSION}\")
|
|
||||||
target_sources(neochat PRIVATE neochataccountregistry.cpp)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
||||||
|
|
||||||
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
|
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
|
||||||
@@ -95,6 +90,7 @@ endif()
|
|||||||
|
|
||||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
|
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
|
||||||
|
|
||||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||||
|
|
||||||
if(NEOCHAT_FLATPAK)
|
if(NEOCHAT_FLATPAK)
|
||||||
@@ -175,6 +171,10 @@ if(ANDROID)
|
|||||||
"window-new"
|
"window-new"
|
||||||
"globe"
|
"globe"
|
||||||
"visibility"
|
"visibility"
|
||||||
|
"home"
|
||||||
|
"preferences-desktop-notification"
|
||||||
|
"computer-symbolic"
|
||||||
|
"gps"
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(neochat PUBLIC Qt::Widgets KF${QT_MAJOR_VERSION}::KIOWidgets)
|
target_link_libraries(neochat PUBLIC Qt::Widgets KF${QT_MAJOR_VERSION}::KIOWidgets)
|
||||||
|
|||||||
@@ -20,25 +20,10 @@
|
|||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
|
#include "texthandler.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
QString markdownToHTML(const QString &markdown)
|
|
||||||
{
|
|
||||||
const auto str = markdown.toUtf8();
|
|
||||||
char *tmp_buf = cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_HARDBREAKS | CMARK_OPT_UNSAFE);
|
|
||||||
|
|
||||||
const std::string html(tmp_buf);
|
|
||||||
|
|
||||||
free(tmp_buf);
|
|
||||||
|
|
||||||
auto result = QString::fromStdString(html).trimmed();
|
|
||||||
|
|
||||||
result.replace("<!-- raw HTML omitted -->", "");
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActionsHandler::ActionsHandler(QObject *parent)
|
ActionsHandler::ActionsHandler(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
@@ -169,7 +154,10 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
handledText = CustomEmojiModel::instance().preprocessText(handledText);
|
handledText = CustomEmojiModel::instance().preprocessText(handledText);
|
||||||
handledText = markdownToHTML(handledText);
|
TextHandler textHandler;
|
||||||
|
textHandler.setData(handledText);
|
||||||
|
handledText = textHandler.handleSendText();
|
||||||
|
|
||||||
if (handledText.count("<p>") == 1 && handledText.count("</p>") == 1) {
|
if (handledText.count("<p>") == 1 && handledText.count("</p>") == 1) {
|
||||||
handledText.remove("<p>");
|
handledText.remove("<p>");
|
||||||
handledText.remove("</p>");
|
handledText.remove("</p>");
|
||||||
|
|||||||
@@ -50,5 +50,3 @@ private:
|
|||||||
QString handleMentions(QString handledText, const bool &isEdit = false);
|
QString handleMentions(QString handledText, const bool &isEdit = false);
|
||||||
void handleMessage(const QString &text, QString handledText, const bool &isEdit = false);
|
void handleMessage(const QString &text, QString handledText, const bool &isEdit = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
QString markdownToHTML(const QString &markdown);
|
|
||||||
|
|||||||
@@ -138,12 +138,13 @@ int ChatDocumentHandler::completionStartIndex() const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
#if !defined(Q_OS_ANDROID) && QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
||||||
const long long cursor = cursorPosition();
|
const long long cursor = cursorPosition();
|
||||||
#else
|
#else
|
||||||
const auto cursor = cursorPosition();
|
const auto cursor = cursorPosition();
|
||||||
#endif
|
#endif
|
||||||
const auto &text = getText();
|
const auto &text = getText();
|
||||||
|
|
||||||
auto start = std::min(cursor, text.size()) - 1;
|
auto start = std::min(cursor, text.size()) - 1;
|
||||||
while (start > -1) {
|
while (start > -1) {
|
||||||
if (text.at(start) == QLatin1Char(' ')) {
|
if (text.at(start) == QLatin1Char(' ')) {
|
||||||
@@ -324,3 +325,35 @@ void ChatDocumentHandler::pushMention(const Mention mention) const
|
|||||||
m_room->mentions()->push_back(mention);
|
m_room->mentions()->push_back(mention);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QColor ChatDocumentHandler::mentionColor() const
|
||||||
|
{
|
||||||
|
return m_mentionColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatDocumentHandler::setMentionColor(const QColor &color)
|
||||||
|
{
|
||||||
|
if (m_mentionColor == color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_mentionColor = color;
|
||||||
|
m_highlighter->mentionFormat.setForeground(m_mentionColor);
|
||||||
|
m_highlighter->rehighlight();
|
||||||
|
Q_EMIT mentionColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ChatDocumentHandler::errorColor() const
|
||||||
|
{
|
||||||
|
return m_errorColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatDocumentHandler::setErrorColor(const QColor &color)
|
||||||
|
{
|
||||||
|
if (m_errorColor == color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_errorColor = color;
|
||||||
|
m_highlighter->errorFormat.setForeground(m_errorColor);
|
||||||
|
m_highlighter->rehighlight();
|
||||||
|
Q_EMIT errorColorChanged();
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ class ChatDocumentHandler : public QObject
|
|||||||
|
|
||||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(QColor mentionColor READ mentionColor WRITE setMentionColor NOTIFY mentionColorChanged);
|
||||||
|
Q_PROPERTY(QColor errorColor READ errorColor WRITE setErrorColor NOTIFY errorColorChanged);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
||||||
|
|
||||||
@@ -60,6 +63,13 @@ public:
|
|||||||
|
|
||||||
void updateCompletions();
|
void updateCompletions();
|
||||||
CompletionModel *completionModel() const;
|
CompletionModel *completionModel() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QColor mentionColor() const;
|
||||||
|
void setMentionColor(const QColor &color);
|
||||||
|
|
||||||
|
[[nodiscard]] QColor errorColor() const;
|
||||||
|
void setErrorColor(const QColor &color);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void isEditChanged();
|
void isEditChanged();
|
||||||
void documentChanged();
|
void documentChanged();
|
||||||
@@ -68,6 +78,8 @@ Q_SIGNALS:
|
|||||||
void completionModelChanged();
|
void completionModelChanged();
|
||||||
void selectionStartChanged();
|
void selectionStartChanged();
|
||||||
void selectionEndChanged();
|
void selectionEndChanged();
|
||||||
|
void errorColorChanged();
|
||||||
|
void mentionColorChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int completionStartIndex() const;
|
int completionStartIndex() const;
|
||||||
@@ -79,6 +91,9 @@ private:
|
|||||||
NeoChatRoom *m_room = nullptr;
|
NeoChatRoom *m_room = nullptr;
|
||||||
bool completionVisible = false;
|
bool completionVisible = false;
|
||||||
|
|
||||||
|
QColor m_mentionColor;
|
||||||
|
QColor m_errorColor;
|
||||||
|
|
||||||
int m_cursorPosition;
|
int m_cursorPosition;
|
||||||
int m_selectionStart;
|
int m_selectionStart;
|
||||||
int m_selectionEnd;
|
int m_selectionEnd;
|
||||||
|
|||||||
@@ -31,11 +31,7 @@
|
|||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
#include <accountregistry.h>
|
||||||
#include "accountregistry.h"
|
|
||||||
#else
|
|
||||||
#include "neochataccountregistry.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <connection.h>
|
#include <connection.h>
|
||||||
#include <csapi/content-repo.h>
|
#include <csapi/content-repo.h>
|
||||||
@@ -43,11 +39,8 @@
|
|||||||
#include <csapi/profile.h>
|
#include <csapi/profile.h>
|
||||||
#include <jobs/downloadfilejob.h>
|
#include <jobs/downloadfilejob.h>
|
||||||
#include <qt_connection_util.h>
|
#include <qt_connection_util.h>
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include <csapi/notifications.h>
|
#include <csapi/notifications.h>
|
||||||
#include <eventstats.h>
|
#include <eventstats.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
@@ -56,6 +49,8 @@
|
|||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "windowcontroller.h"
|
#include "windowcontroller.h"
|
||||||
|
|
||||||
|
#include <accountregistry.h>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||||
#include "trayicon.h"
|
#include "trayicon.h"
|
||||||
#elif !defined(Q_OS_ANDROID)
|
#elif !defined(Q_OS_ANDROID)
|
||||||
@@ -93,10 +88,28 @@ Controller::Controller(QObject *parent)
|
|||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this] {
|
connectUntil(&Accounts, &AccountRegistry::rowsInserted, this, [this]() {
|
||||||
invokeLogin();
|
if (auto *connection = Accounts.get(NeoChatConfig::self()->activeConnection())) {
|
||||||
|
connectSingleShot(connection, &Connection::loadedRoomState, this, [this, connection]() {
|
||||||
|
setActiveConnection(connection);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(&Accounts, &AccountRegistry::rowsRemoved, this, [this]() {
|
||||||
|
if (!Accounts.isLoggedIn(NeoChatConfig::self()->activeConnection())) {
|
||||||
|
if (Accounts.size() > 0) {
|
||||||
|
setActiveConnection(Accounts.accounts().at(0));
|
||||||
|
} else {
|
||||||
|
setActiveConnection(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(&Accounts, &AccountRegistry::invokeLogin);
|
||||||
|
|
||||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [] {
|
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [] {
|
||||||
NeoChatConfig::self()->save();
|
NeoChatConfig::self()->save();
|
||||||
});
|
});
|
||||||
@@ -125,23 +138,29 @@ Controller::Controller(QObject *parent)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
connect(&AccountRegistry::instance(), &AccountRegistry::accountCountChanged, this, &Controller::activeConnectionIndexChanged);
|
connect(&Accounts, &AccountRegistry::accountCountChanged, this, &Controller::activeConnectionIndexChanged);
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
static int oldAccountCount = 0;
|
static int oldAccountCount = 0;
|
||||||
connect(&AccountRegistry::instance(), &AccountRegistry::accountCountChanged, this, [=]() {
|
connect(&Accounts, &AccountRegistry::accountCountChanged, this, [this]() {
|
||||||
if (AccountRegistry::instance().size() > oldAccountCount) {
|
if (Accounts.size() > oldAccountCount) {
|
||||||
auto connection = AccountRegistry::instance().accounts()[AccountRegistry::instance().size() - 1];
|
auto connection = Accounts.accounts()[Accounts.size() - 1];
|
||||||
connect(connection, &Connection::syncDone, this, [=]() {
|
connect(connection, &Connection::syncDone, this, [this, connection]() {
|
||||||
handleNotifications(connection);
|
bool changes = false;
|
||||||
|
for (const auto &room : connection->allRooms()) {
|
||||||
|
if (m_notificationCounts[room] != room->unreadStats().notableCount) {
|
||||||
|
m_notificationCounts[room] = room->unreadStats().notableCount;
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changes) {
|
||||||
|
handleNotifications(connection);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
oldAccountCount = AccountRegistry::instance().size();
|
oldAccountCount = Accounts.size();
|
||||||
});
|
});
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
||||||
{
|
{
|
||||||
static QStringList initial;
|
static QStringList initial;
|
||||||
@@ -215,7 +234,6 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
Controller &Controller::instance()
|
Controller &Controller::instance()
|
||||||
{
|
{
|
||||||
@@ -228,239 +246,10 @@ void Controller::showWindow()
|
|||||||
WindowController::instance().showAndRaiseWindow(QString());
|
WindowController::instance().showAndRaiseWindow(QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::loginWithAccessToken(const QString &serverAddr, const QString &user, const QString &token, const QString &deviceName)
|
|
||||||
{
|
|
||||||
if (user.isEmpty() || token.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl serverUrl(serverAddr);
|
|
||||||
|
|
||||||
auto conn = new Connection();
|
|
||||||
if (serverUrl.isValid()) {
|
|
||||||
conn->setHomeserver(serverUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(conn, &Connection::connected, this, [this, conn, deviceName] {
|
|
||||||
AccountSettings account(conn->userId());
|
|
||||||
account.setKeepLoggedIn(true);
|
|
||||||
account.setHomeserver(conn->homeserver());
|
|
||||||
account.setDeviceId(conn->deviceId());
|
|
||||||
account.setDeviceName(deviceName);
|
|
||||||
if (!saveAccessTokenToKeyChain(account, conn->accessToken())) {
|
|
||||||
qWarning() << "Couldn't save access token";
|
|
||||||
}
|
|
||||||
account.sync();
|
|
||||||
addConnection(conn);
|
|
||||||
setActiveConnection(conn);
|
|
||||||
});
|
|
||||||
connect(conn, &Connection::networkError, this, [this](QString error, const QString &, int, int) {
|
|
||||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
|
||||||
});
|
|
||||||
conn->assumeIdentity(user, token, deviceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::logout(Connection *conn, bool serverSideLogout)
|
|
||||||
{
|
|
||||||
if (!conn) {
|
|
||||||
qCritical() << "Attempt to logout null connection";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsGroup("Accounts").remove(conn->userId());
|
|
||||||
|
|
||||||
QKeychain::DeletePasswordJob job(qAppName());
|
|
||||||
job.setAutoDelete(true);
|
|
||||||
job.setKey(conn->userId());
|
|
||||||
QEventLoop loop;
|
|
||||||
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
|
||||||
job.start();
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (AccountRegistry::instance().count() > 1) {
|
|
||||||
// Only set the connection if the the account being logged out is currently active
|
|
||||||
if (conn == activeConnection()) {
|
|
||||||
setActiveConnection(AccountRegistry::instance().accounts()[0]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setActiveConnection(nullptr);
|
|
||||||
}
|
|
||||||
if (!serverSideLogout) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
conn->logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::addConnection(Connection *c)
|
|
||||||
{
|
|
||||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
|
|
||||||
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
AccountRegistry::instance().add(c);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
c->setLazyLoading(true);
|
|
||||||
|
|
||||||
connect(c, &Connection::syncDone, this, [this, c] {
|
|
||||||
setBusy(false);
|
|
||||||
|
|
||||||
Q_EMIT syncDone();
|
|
||||||
|
|
||||||
c->sync(30000);
|
|
||||||
c->saveState();
|
|
||||||
});
|
|
||||||
connect(c, &Connection::loggedOut, this, [this, c] {
|
|
||||||
dropConnection(c);
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(c, &Connection::requestFailed, this, [this](BaseJob *job) {
|
|
||||||
if (job->error() == BaseJob::UserConsentRequiredError) {
|
|
||||||
Q_EMIT userConsentRequired(job->errorUrl());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setBusy(true);
|
|
||||||
|
|
||||||
c->sync();
|
|
||||||
|
|
||||||
Q_EMIT connectionAdded(c);
|
|
||||||
Q_EMIT accountCountChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::dropConnection(Connection *c)
|
|
||||||
{
|
|
||||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
|
|
||||||
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
AccountRegistry::instance().drop(c);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Q_EMIT connectionDropped(c);
|
|
||||||
Q_EMIT accountCountChanged();
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
c->deleteLater();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::invokeLogin()
|
|
||||||
{
|
|
||||||
const auto accounts = SettingsGroup("Accounts").childGroups();
|
|
||||||
QString id = NeoChatConfig::self()->activeConnection();
|
|
||||||
for (const auto &accountId : accounts) {
|
|
||||||
AccountSettings account{accountId};
|
|
||||||
if (id.isEmpty()) {
|
|
||||||
// handle case where the account config is empty
|
|
||||||
id = accountId;
|
|
||||||
}
|
|
||||||
if (!account.homeserver().isEmpty()) {
|
|
||||||
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
|
|
||||||
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, id, this, accessTokenLoadingJob](QKeychain::Job *) {
|
|
||||||
AccountSettings account{accountId};
|
|
||||||
QString accessToken;
|
|
||||||
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
|
|
||||||
accessToken = accessTokenLoadingJob->binaryData();
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto connection = new Connection(account.homeserver());
|
|
||||||
connect(connection, &Connection::connected, this, [this, connection, id] {
|
|
||||||
connection->loadState();
|
|
||||||
addConnection(connection);
|
|
||||||
if (connection->userId() == id) {
|
|
||||||
setActiveConnection(connection);
|
|
||||||
connectSingleShot(connection, &Connection::syncDone, this, &Controller::initiated);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
connect(connection, &Connection::loginError, this, [this, connection](const QString &error, const QString &) {
|
|
||||||
if (error == "Unrecognised access token") {
|
|
||||||
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
|
|
||||||
logout(connection, false);
|
|
||||||
} else if (error == "Connection closed") {
|
|
||||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
|
||||||
// Failed due to network connection issue. This might happen when the homeserver is
|
|
||||||
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
|
|
||||||
// connect to the homeserver. In this case, we don't want to do logout().
|
|
||||||
} else {
|
|
||||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
|
||||||
logout(connection, true);
|
|
||||||
}
|
|
||||||
Q_EMIT initiated();
|
|
||||||
});
|
|
||||||
connect(connection, &Connection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
|
||||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
|
||||||
});
|
|
||||||
connection->assumeIdentity(account.userId(), accessToken, account.deviceId());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (accounts.isEmpty()) {
|
|
||||||
Q_EMIT initiated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
|
|
||||||
{
|
|
||||||
qDebug() << "Reading access token from the keychain for" << account.userId();
|
|
||||||
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
|
|
||||||
job->setKey(account.userId());
|
|
||||||
|
|
||||||
// Handling of errors
|
|
||||||
connect(job, &QKeychain::Job::finished, this, [this, &account, job]() {
|
|
||||||
if (job->error() == QKeychain::Error::NoError) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (job->error()) {
|
|
||||||
case QKeychain::EntryNotFound:
|
|
||||||
Q_EMIT globalErrorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
|
|
||||||
break;
|
|
||||||
case QKeychain::AccessDeniedByUser:
|
|
||||||
case QKeychain::AccessDenied:
|
|
||||||
Q_EMIT globalErrorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
|
|
||||||
break;
|
|
||||||
case QKeychain::NoBackendAvailable:
|
|
||||||
Q_EMIT globalErrorOccured(i18n("No keychain available."), i18n("Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
|
|
||||||
break;
|
|
||||||
case QKeychain::OtherError:
|
|
||||||
Q_EMIT globalErrorOccured(i18n("Unable to read access token"), job->errorString());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
job->start();
|
|
||||||
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
|
|
||||||
{
|
|
||||||
qDebug() << "Save the access token to the keychain for " << account.userId();
|
|
||||||
QKeychain::WritePasswordJob job(qAppName());
|
|
||||||
job.setAutoDelete(false);
|
|
||||||
job.setKey(account.userId());
|
|
||||||
job.setBinaryData(accessToken);
|
|
||||||
QEventLoop loop;
|
|
||||||
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
|
||||||
job.start();
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
if (job.error()) {
|
|
||||||
qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::changeAvatar(Connection *conn, const QUrl &localFile)
|
void Controller::changeAvatar(Connection *conn, const QUrl &localFile)
|
||||||
{
|
{
|
||||||
auto job = conn->uploadFile(localFile.toLocalFile());
|
auto job = conn->uploadFile(localFile.toLocalFile());
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
if (isJobPending(job)) {
|
if (isJobPending(job)) {
|
||||||
#else
|
|
||||||
if (isJobRunning(job)) {
|
|
||||||
#endif
|
|
||||||
connect(job, &BaseJob::success, this, [conn, job] {
|
connect(job, &BaseJob::success, this, [conn, job] {
|
||||||
conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
|
conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
|
||||||
});
|
});
|
||||||
@@ -487,7 +276,7 @@ bool Controller::supportSystemTray() const
|
|||||||
|
|
||||||
void Controller::changePassword(Connection *connection, const QString ¤tPassword, const QString &newPassword)
|
void Controller::changePassword(Connection *connection, const QString ¤tPassword, const QString &newPassword)
|
||||||
{
|
{
|
||||||
NeochatChangePasswordJob *job = connection->callApi<NeochatChangePasswordJob>(newPassword, false);
|
auto *job = connection->callApi<NeochatChangePasswordJob>(newPassword, false);
|
||||||
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword, connection] {
|
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword, connection] {
|
||||||
if (job->error() == 103) {
|
if (job->error() == 103) {
|
||||||
QJsonObject replyData = job->jsonData();
|
QJsonObject replyData = job->jsonData();
|
||||||
@@ -498,7 +287,7 @@ void Controller::changePassword(Connection *connection, const QString ¤tPa
|
|||||||
authData["user"] = connection->user()->id();
|
authData["user"] = connection->user()->id();
|
||||||
QJsonObject identifier = {{"type", "m.id.user"}, {"user", connection->user()->id()}};
|
QJsonObject identifier = {{"type", "m.id.user"}, {"user", connection->user()->id()}};
|
||||||
authData["identifier"] = identifier;
|
authData["identifier"] = identifier;
|
||||||
NeochatChangePasswordJob *innerJob = connection->callApi<NeochatChangePasswordJob>(newPassword, false, authData);
|
auto *innerJob = connection->callApi<NeochatChangePasswordJob>(newPassword, false, authData);
|
||||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||||
Q_EMIT passwordStatus(PasswordStatus::Success);
|
Q_EMIT passwordStatus(PasswordStatus::Success);
|
||||||
});
|
});
|
||||||
@@ -518,11 +307,7 @@ bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
|
|||||||
User *localUser = connection->user();
|
User *localUser = connection->user();
|
||||||
QString decoded = avatarSource.path();
|
QString decoded = avatarSource.path();
|
||||||
if (decoded.isEmpty()) {
|
if (decoded.isEmpty()) {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
connection->callApi<SetAvatarUrlJob>(localUser->id(), avatarSource);
|
connection->callApi<SetAvatarUrlJob>(localUser->id(), avatarSource);
|
||||||
#else
|
|
||||||
connection->callApi<SetAvatarUrlJob>(localUser->id(), QString());
|
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (QImageReader(decoded).read().isNull()) {
|
if (QImageReader(decoded).read().isNull()) {
|
||||||
@@ -533,11 +318,7 @@ bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
|
|||||||
}
|
}
|
||||||
|
|
||||||
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
|
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password")
|
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password")
|
||||||
#else
|
|
||||||
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), QStringLiteral("/_matrix/client/r0/account/password"))
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
QJsonObject _data;
|
QJsonObject _data;
|
||||||
addParam<>(_data, QStringLiteral("new_password"), newPassword);
|
addParam<>(_data, QStringLiteral("new_password"), newPassword);
|
||||||
@@ -546,9 +327,12 @@ NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, b
|
|||||||
setRequestData(_data);
|
setRequestData(_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Controller::accountCount() const
|
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
|
||||||
|
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/matrix/client/r0/devices/%1").arg(deviceId).toLatin1())
|
||||||
{
|
{
|
||||||
return AccountRegistry::instance().count();
|
QJsonObject _data;
|
||||||
|
addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
|
||||||
|
setRequestData(std::move(_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Controller::quitOnLastWindowClosed()
|
bool Controller::quitOnLastWindowClosed()
|
||||||
@@ -629,18 +413,6 @@ void Controller::saveWindowGeometry()
|
|||||||
WindowController::instance().saveGeometry();
|
WindowController::instance().saveGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId).toLatin1())
|
|
||||||
#else
|
|
||||||
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
QJsonObject _data;
|
|
||||||
addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
|
|
||||||
setRequestData(std::move(_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::createRoom(const QString &name, const QString &topic)
|
void Controller::createRoom(const QString &name, const QString &topic)
|
||||||
{
|
{
|
||||||
auto createRoomJob = m_connection->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
auto createRoomJob = m_connection->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
||||||
@@ -651,7 +423,7 @@ void Controller::createRoom(const QString &name, const QString &topic)
|
|||||||
this,
|
this,
|
||||||
&Controller::roomAdded,
|
&Controller::roomAdded,
|
||||||
this,
|
this,
|
||||||
[this](NeoChatRoom *room) {
|
[](NeoChatRoom *room) {
|
||||||
RoomManager::instance().enterRoom(room);
|
RoomManager::instance().enterRoom(room);
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
@@ -731,11 +503,7 @@ QString Controller::plainText(QQuickTextDocument *document) const
|
|||||||
|
|
||||||
bool Controller::encryptionSupported() const
|
bool Controller::encryptionSupported() const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return Quotient::encryptionSupported();
|
return Quotient::encryptionSupported();
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
|
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
|
||||||
@@ -776,29 +544,10 @@ void Controller::setApplicationProxy()
|
|||||||
|
|
||||||
int Controller::activeConnectionIndex() const
|
int Controller::activeConnectionIndex() const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
auto result = std::find_if(Accounts.accounts().begin(), Accounts.accounts().end(), [this](const auto &it) {
|
auto result = std::find_if(Accounts.accounts().begin(), Accounts.accounts().end(), [this](const auto &it) {
|
||||||
return it == m_connection;
|
return it == m_connection;
|
||||||
});
|
});
|
||||||
return result - Accounts.accounts().begin();
|
return result - Accounts.accounts().begin();
|
||||||
#else
|
|
||||||
for (int i = 0; i < AccountRegistry::instance().rowCount(); i++) {
|
|
||||||
if (AccountRegistry::instance().data(AccountRegistry::instance().index(i, 0), AccountRegistry::UserIdRole).toString() == m_connection->userId()) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int Controller::quotientMinorVersion() const
|
|
||||||
{
|
|
||||||
// TODO libQuotient 0.7: Replace with version function from libQuotient
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return 7;
|
|
||||||
#else
|
|
||||||
return 6;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Controller::isFlatpak() const
|
bool Controller::isFlatpak() const
|
||||||
|
|||||||
@@ -23,15 +23,9 @@ class Connection;
|
|||||||
class Room;
|
class Room;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace QKeychain
|
|
||||||
{
|
|
||||||
class ReadPasswordJob;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Controller : public QObject
|
class Controller : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(int accountCount READ accountCount NOTIFY accountCountChanged)
|
|
||||||
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
|
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
|
||||||
Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||||
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
||||||
@@ -40,7 +34,6 @@ class Controller : public QObject
|
|||||||
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
||||||
Q_PROPERTY(bool encryptionSupported READ encryptionSupported CONSTANT)
|
Q_PROPERTY(bool encryptionSupported READ encryptionSupported CONSTANT)
|
||||||
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
|
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
|
||||||
Q_PROPERTY(int quotientMinorVersion READ quotientMinorVersion CONSTANT)
|
|
||||||
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
|
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -49,17 +42,10 @@ public:
|
|||||||
void setActiveConnection(Quotient::Connection *connection);
|
void setActiveConnection(Quotient::Connection *connection);
|
||||||
[[nodiscard]] Quotient::Connection *activeConnection() const;
|
[[nodiscard]] Quotient::Connection *activeConnection() const;
|
||||||
|
|
||||||
void addConnection(Quotient::Connection *c);
|
|
||||||
void dropConnection(Quotient::Connection *c);
|
|
||||||
|
|
||||||
Q_INVOKABLE void loginWithAccessToken(const QString &, const QString &, const QString &, const QString &);
|
|
||||||
|
|
||||||
Q_INVOKABLE void changePassword(Quotient::Connection *connection, const QString ¤tPassword, const QString &newPassword);
|
Q_INVOKABLE void changePassword(Quotient::Connection *connection, const QString ¤tPassword, const QString &newPassword);
|
||||||
|
|
||||||
Q_INVOKABLE bool setAvatar(Quotient::Connection *connection, const QUrl &avatarSource);
|
Q_INVOKABLE bool setAvatar(Quotient::Connection *connection, const QUrl &avatarSource);
|
||||||
|
|
||||||
[[nodiscard]] int accountCount() const;
|
|
||||||
|
|
||||||
[[nodiscard]] static bool quitOnLastWindowClosed();
|
[[nodiscard]] static bool quitOnLastWindowClosed();
|
||||||
void setQuitOnLastWindowClosed(bool value);
|
void setQuitOnLastWindowClosed(bool value);
|
||||||
|
|
||||||
@@ -68,8 +54,6 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] bool supportSystemTray() const;
|
[[nodiscard]] bool supportSystemTray() const;
|
||||||
|
|
||||||
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
|
||||||
|
|
||||||
int activeConnectionIndex() const;
|
int activeConnectionIndex() const;
|
||||||
|
|
||||||
enum PasswordStatus {
|
enum PasswordStatus {
|
||||||
@@ -100,7 +84,6 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void setApplicationProxy();
|
Q_INVOKABLE void setApplicationProxy();
|
||||||
|
|
||||||
int quotientMinorVersion() const;
|
|
||||||
bool isFlatpak() const;
|
bool isFlatpak() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -110,20 +93,15 @@ private:
|
|||||||
bool m_busy = false;
|
bool m_busy = false;
|
||||||
TrayIcon *m_trayIcon = nullptr;
|
TrayIcon *m_trayIcon = nullptr;
|
||||||
|
|
||||||
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
|
|
||||||
|
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings() const;
|
void saveSettings() const;
|
||||||
bool m_isOnline = true;
|
bool m_isOnline = true;
|
||||||
QMap<Quotient::Room *, int> m_notificationCounts;
|
QMap<Quotient::Room *, int> m_notificationCounts;
|
||||||
|
|
||||||
bool hasWindowSystem() const;
|
bool hasWindowSystem() const;
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
void handleNotifications(QPointer<Quotient::Connection> connection);
|
void handleNotifications(QPointer<Quotient::Connection> connection);
|
||||||
#endif
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void invokeLogin();
|
|
||||||
void showWindow();
|
void showWindow();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
@@ -136,7 +114,6 @@ Q_SIGNALS:
|
|||||||
void syncDone();
|
void syncDone();
|
||||||
void connectionAdded(Quotient::Connection *_t1);
|
void connectionAdded(Quotient::Connection *_t1);
|
||||||
void connectionDropped(Quotient::Connection *_t1);
|
void connectionDropped(Quotient::Connection *_t1);
|
||||||
void accountCountChanged();
|
|
||||||
void initiated();
|
void initiated();
|
||||||
void notificationClicked(const QString &_t1, const QString &_t2);
|
void notificationClicked(const QString &_t1, const QString &_t2);
|
||||||
void quitOnLastWindowClosedChanged();
|
void quitOnLastWindowClosedChanged();
|
||||||
@@ -154,13 +131,12 @@ Q_SIGNALS:
|
|||||||
void roomAdded(NeoChatRoom *room);
|
void roomAdded(NeoChatRoom *room);
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void logout(Quotient::Connection *conn, bool serverSideLogout);
|
|
||||||
void changeAvatar(Quotient::Connection *conn, const QUrl &localFile);
|
void changeAvatar(Quotient::Connection *conn, const QUrl &localFile);
|
||||||
static void markAllMessagesAsRead(Quotient::Connection *conn);
|
static void markAllMessagesAsRead(Quotient::Connection *conn);
|
||||||
void saveWindowGeometry();
|
void saveWindowGeometry();
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO libQuotient 0.7: Drop
|
// TODO libQuotient 0.?: Drop
|
||||||
class NeochatChangePasswordJob : public Quotient::BaseJob
|
class NeochatChangePasswordJob : public Quotient::BaseJob
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ void FileTransferPseudoJob::fileTransferProgress(QString id, qint64 progress, qi
|
|||||||
|
|
||||||
void FileTransferPseudoJob::fileTransferCompleted(QString id, QUrl localFile)
|
void FileTransferPseudoJob::fileTransferCompleted(QString id, QUrl localFile)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(localFile);
|
||||||
if (id != m_eventId) {
|
if (id != m_eventId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,30 +7,17 @@
|
|||||||
|
|
||||||
namespace Quotient
|
namespace Quotient
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
class JoinRulesEvent : public StateEvent
|
class JoinRulesEvent : public StateEvent
|
||||||
#else
|
|
||||||
class JoinRulesEvent : public StateEventBase
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
QUO_EVENT(JoinRulesEvent, "m.room.join_rules")
|
QUO_EVENT(JoinRulesEvent, "m.room.join_rules")
|
||||||
#else
|
|
||||||
DEFINE_EVENT_TYPEID("m.room.join_rules", JoinRulesEvent)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
explicit JoinRulesEvent(const QJsonObject &obj)
|
explicit JoinRulesEvent(const QJsonObject &obj)
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
: StateEvent(obj)
|
: StateEvent(obj)
|
||||||
#else
|
|
||||||
: StateEventBase(typeId(), obj)
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QString joinRule() const;
|
QString joinRule() const;
|
||||||
QJsonArray allow() const;
|
QJsonArray allow() const;
|
||||||
};
|
};
|
||||||
REGISTER_EVENT_TYPE(JoinRulesEvent)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,13 @@
|
|||||||
|
|
||||||
#include "login.h"
|
#include "login.h"
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include <accountregistry.h>
|
#include <accountregistry.h>
|
||||||
#else
|
|
||||||
#include "neochataccountregistry.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <connection.h>
|
#include <connection.h>
|
||||||
#include <qt_connection_util.h>
|
#include <qt_connection_util.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
@@ -43,7 +40,7 @@ void Login::init()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_isLoggedIn = AccountRegistry::instance().isLoggedIn(m_matrixId);
|
m_isLoggedIn = Accounts.isLoggedIn(m_matrixId);
|
||||||
Q_EMIT isLoggedInChanged();
|
Q_EMIT isLoggedInChanged();
|
||||||
if (m_isLoggedIn) {
|
if (m_isLoggedIn) {
|
||||||
return;
|
return;
|
||||||
@@ -74,11 +71,7 @@ void Login::init()
|
|||||||
account.setHomeserver(m_connection->homeserver());
|
account.setHomeserver(m_connection->homeserver());
|
||||||
account.setDeviceId(m_connection->deviceId());
|
account.setDeviceId(m_connection->deviceId());
|
||||||
account.setDeviceName(m_deviceName);
|
account.setDeviceName(m_deviceName);
|
||||||
if (!Controller::instance().saveAccessTokenToKeyChain(account, m_connection->accessToken())) {
|
|
||||||
qWarning() << "Couldn't save access token";
|
|
||||||
}
|
|
||||||
account.sync();
|
account.sync();
|
||||||
Controller::instance().addConnection(m_connection);
|
|
||||||
Controller::instance().setActiveConnection(m_connection);
|
Controller::instance().setActiveConnection(m_connection);
|
||||||
m_connection = nullptr;
|
m_connection = nullptr;
|
||||||
});
|
});
|
||||||
@@ -97,8 +90,9 @@ void Login::init()
|
|||||||
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
||||||
});
|
});
|
||||||
|
|
||||||
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
|
connectSingleShot(m_connection, &Connection::loadedRoomState, this, [this]() {
|
||||||
Q_EMIT Controller::instance().initiated();
|
Controller::instance().setActiveConnection(m_connection);
|
||||||
|
// TODO close settings window
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
src/main.cpp
31
src/main.cpp
@@ -28,15 +28,14 @@
|
|||||||
|
|
||||||
#include "neochat-version.h"
|
#include "neochat-version.h"
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include <accountregistry.h>
|
#include <accountregistry.h>
|
||||||
#else
|
|
||||||
#include "neochataccountregistry.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <networkaccessmanager.h>
|
#include <networkaccessmanager.h>
|
||||||
#include <room.h>
|
#include <room.h>
|
||||||
#include <util.h>
|
#include <util.h>
|
||||||
|
#include <keyverificationsession.h>
|
||||||
|
#include <accountregistry.h>
|
||||||
|
#include <room.h>
|
||||||
|
#include <networkaccessmanager.h>
|
||||||
|
|
||||||
#include "actionshandler.h"
|
#include "actionshandler.h"
|
||||||
#include "blurhashimageprovider.h"
|
#include "blurhashimageprovider.h"
|
||||||
@@ -44,7 +43,6 @@
|
|||||||
#include "clipboard.h"
|
#include "clipboard.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "filetypesingleton.h"
|
#include "filetypesingleton.h"
|
||||||
#include "joinrulesevent.h"
|
|
||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
#include "login.h"
|
#include "login.h"
|
||||||
#include "matriximageprovider.h"
|
#include "matriximageprovider.h"
|
||||||
@@ -69,16 +67,11 @@
|
|||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "notificationsmanager.h"
|
#include "notificationsmanager.h"
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
#endif
|
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "spacehierarchycache.h"
|
#include "spacehierarchycache.h"
|
||||||
#include "urlhelper.h"
|
#include "urlhelper.h"
|
||||||
#include "windowcontroller.h"
|
#include "windowcontroller.h"
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include <keyverificationsession.h>
|
|
||||||
#endif
|
|
||||||
#ifdef HAVE_COLORSCHEME
|
#ifdef HAVE_COLORSCHEME
|
||||||
#include "colorschemer.h"
|
#include "colorschemer.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -86,6 +79,7 @@
|
|||||||
#include "models/statemodel.h"
|
#include "models/statemodel.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
|
|
||||||
|
|
||||||
#ifdef HAVE_RUNNER
|
#ifdef HAVE_RUNNER
|
||||||
#include "runner.h"
|
#include "runner.h"
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
@@ -166,14 +160,10 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
about.addComponent(QStringLiteral("libQuotient"),
|
about.addComponent(QStringLiteral("libQuotient"),
|
||||||
i18n("A Qt5 library to write cross-platform clients for Matrix"),
|
i18n("A Qt5 library to write cross-platform clients for Matrix"),
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
i18nc("<version number> (built against <possibly different version number>)",
|
i18nc("<version number> (built against <possibly different version number>)",
|
||||||
"%1 (built against %2)",
|
"%1 (built against %2)",
|
||||||
Quotient::versionString(),
|
Quotient::versionString(),
|
||||||
QStringLiteral(Quotient_VERSION_STRING)),
|
QStringLiteral(Quotient_VERSION_STRING)),
|
||||||
#else
|
|
||||||
QStringLiteral(QUOTIENT_VERSION),
|
|
||||||
#endif
|
|
||||||
QStringLiteral("https://github.com/quotient-im/libquotient"),
|
QStringLiteral("https://github.com/quotient-im/libquotient"),
|
||||||
KAboutLicense::LGPL_V2_1);
|
KAboutLicense::LGPL_V2_1);
|
||||||
|
|
||||||
@@ -210,11 +200,8 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "UrlHelper", &urlHelper);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "UrlHelper", &urlHelper);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "EmojiModel", &EmojiModel::instance());
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "EmojiModel", &EmojiModel::instance());
|
||||||
#ifdef QUOTIENT_07
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Accounts", &Quotient::Accounts);
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Quotient::Accounts);
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Accounts", &Quotient::Accounts);
|
||||||
#else
|
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Quotient::AccountRegistry::instance());
|
|
||||||
#endif
|
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "SpaceHierarchyCache", &SpaceHierarchyCache::instance());
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "SpaceHierarchyCache", &SpaceHierarchyCache::instance());
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "CustomEmojiModel", &CustomEmojiModel::instance());
|
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "CustomEmojiModel", &CustomEmojiModel::instance());
|
||||||
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
|
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
|
||||||
@@ -236,9 +223,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
||||||
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
||||||
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||||
#endif
|
|
||||||
qmlRegisterType<KeywordNotificationRuleModel>("org.kde.neochat", 1, 0, "KeywordNotificationRuleModel");
|
qmlRegisterType<KeywordNotificationRuleModel>("org.kde.neochat", 1, 0, "KeywordNotificationRuleModel");
|
||||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||||
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
||||||
@@ -257,12 +242,10 @@ int main(int argc, char *argv[])
|
|||||||
qRegisterMetaType<NeoChatUser *>("NeoChatUser*");
|
qRegisterMetaType<NeoChatUser *>("NeoChatUser*");
|
||||||
qRegisterMetaType<GetRoomEventsJob *>("GetRoomEventsJob*");
|
qRegisterMetaType<GetRoomEventsJob *>("GetRoomEventsJob*");
|
||||||
qRegisterMetaType<QMimeType>("QMimeType");
|
qRegisterMetaType<QMimeType>("QMimeType");
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#ifdef Quotient_E2EE_ENABLED
|
#ifdef Quotient_E2EE_ENABLED
|
||||||
qRegisterMetaType<KeyVerificationSession *>("KeyVerificationSession*");
|
qRegisterMetaType<KeyVerificationSession *>("KeyVerificationSession*");
|
||||||
qmlRegisterUncreatableType<KeyVerificationSession>("org.kde.neochat", 1, 0, "KeyVerificationSession", {});
|
qmlRegisterUncreatableType<KeyVerificationSession>("org.kde.neochat", 1, 0, "KeyVerificationSession", {});
|
||||||
qRegisterMetaType<QVector<EmojiEntry>>("QVector<EmojiEntry>");
|
qRegisterMetaType<QVector<EmojiEntry>>("QVector<EmojiEntry>");
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
qmlRegisterSingletonType("org.kde.neochat", 1, 0, "About", [](QQmlEngine *engine, QJSEngine *) -> QJSValue {
|
qmlRegisterSingletonType("org.kde.neochat", 1, 0, "About", [](QQmlEngine *engine, QJSEngine *) -> QJSValue {
|
||||||
return engine->toScriptValue(KAboutData::applicationData());
|
return engine->toScriptValue(KAboutData::applicationData());
|
||||||
|
|||||||
@@ -19,6 +19,41 @@ QStringList rainbowColors{"#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500",
|
|||||||
"#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff",
|
"#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff",
|
||||||
"#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"};
|
"#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"};
|
||||||
|
|
||||||
|
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
Q_EMIT room->showMessage(NeoChatRoom::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(NeoChatRoom::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);
|
||||||
|
if (!leaving) {
|
||||||
|
leaving = room->connection()->roomByAlias(text);
|
||||||
|
}
|
||||||
|
if (leaving) {
|
||||||
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||||
|
room->connection()->leaveRoom(leaving);
|
||||||
|
} else {
|
||||||
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto roomNickLambda = [](const QString &text, NeoChatRoom *room) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
|
||||||
|
} else {
|
||||||
|
room->connection()->user()->rename(text, room);
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
};
|
||||||
|
|
||||||
QVector<ActionsModel::Action> actions{
|
QVector<ActionsModel::Action> actions{
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("shrug"),
|
QStringLiteral("shrug"),
|
||||||
@@ -157,7 +192,6 @@ QVector<ActionsModel::Action> actions{
|
|||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
|
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
|
||||||
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
|
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
||||||
@@ -167,7 +201,6 @@ QVector<ActionsModel::Action> actions{
|
|||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (room->localUser()->id() == text) {
|
if (room->localUser()->id() == text) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18n("You are already in this room."));
|
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18n("You are already in this room."));
|
||||||
return QString();
|
return QString();
|
||||||
@@ -209,7 +242,6 @@ QVector<ActionsModel::Action> actions{
|
|||||||
kli18n("<room alias or id>"),
|
kli18n("<room alias or id>"),
|
||||||
kli18n("Joins the given room"),
|
kli18n("Joins the given room"),
|
||||||
},
|
},
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("knock"),
|
QStringLiteral("knock"),
|
||||||
[](const QString &text, NeoChatRoom *room) {
|
[](const QString &text, NeoChatRoom *room) {
|
||||||
@@ -242,7 +274,6 @@ QVector<ActionsModel::Action> actions{
|
|||||||
kli18n("<room alias or id> [<reason>]"),
|
kli18n("<room alias or id> [<reason>]"),
|
||||||
kli18n("Requests to join the given room"),
|
kli18n("Requests to join the given room"),
|
||||||
},
|
},
|
||||||
#endif
|
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("j"),
|
QStringLiteral("j"),
|
||||||
[](const QString &text, NeoChatRoom *room) {
|
[](const QString &text, NeoChatRoom *room) {
|
||||||
@@ -268,31 +299,7 @@ QVector<ActionsModel::Action> actions{
|
|||||||
},
|
},
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("part"),
|
QStringLiteral("part"),
|
||||||
[](const QString &text, NeoChatRoom *room) {
|
leaveRoomLambda,
|
||||||
if (text.isEmpty()) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::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(NeoChatRoom::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);
|
|
||||||
if (!leaving) {
|
|
||||||
leaving = room->connection()->roomByAlias(text);
|
|
||||||
}
|
|
||||||
if (leaving) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
|
||||||
room->connection()->leaveRoom(leaving);
|
|
||||||
} else {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
},
|
|
||||||
false,
|
false,
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
kli18n("[<room alias or id>]"),
|
kli18n("[<room alias or id>]"),
|
||||||
@@ -300,31 +307,7 @@ QVector<ActionsModel::Action> actions{
|
|||||||
},
|
},
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("leave"),
|
QStringLiteral("leave"),
|
||||||
[](const QString &text, NeoChatRoom *room) {
|
leaveRoomLambda,
|
||||||
if (text.isEmpty()) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::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(NeoChatRoom::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);
|
|
||||||
if (!leaving) {
|
|
||||||
leaving = room->connection()->roomByAlias(text);
|
|
||||||
}
|
|
||||||
if (leaving) {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
|
||||||
room->connection()->leaveRoom(leaving);
|
|
||||||
} else {
|
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
},
|
|
||||||
false,
|
false,
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
kli18n("[<room alias or id>]"),
|
kli18n("[<room alias or id>]"),
|
||||||
@@ -347,14 +330,15 @@ QVector<ActionsModel::Action> actions{
|
|||||||
},
|
},
|
||||||
Action{
|
Action{
|
||||||
QStringLiteral("roomnick"),
|
QStringLiteral("roomnick"),
|
||||||
[](const QString &text, NeoChatRoom *room) {
|
roomNickLambda,
|
||||||
if (text.isEmpty()) {
|
false,
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
|
std::nullopt,
|
||||||
} else {
|
kli18n("<display name>"),
|
||||||
room->connection()->user()->rename(text, room);
|
kli18n("Changes your display name in this room"),
|
||||||
}
|
},
|
||||||
return QString();
|
Action{
|
||||||
},
|
QStringLiteral("myroomnick"),
|
||||||
|
roomNickLambda,
|
||||||
false,
|
false,
|
||||||
std::nullopt,
|
std::nullopt,
|
||||||
kli18n("<display name>"),
|
kli18n("<display name>"),
|
||||||
@@ -448,14 +432,12 @@ QVector<ActionsModel::Action> actions{
|
|||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
auto state = room->currentState().get<RoomMemberEvent>(parts[0]);
|
auto state = room->currentState().get<RoomMemberEvent>(parts[0]);
|
||||||
if (state && state->membership() == Membership::Ban) {
|
if (state && state->membership() == Membership::Ban) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#endif
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
auto plEvent = room->getCurrentState<RoomPowerLevelsEvent>();
|
|
||||||
if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
|
if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to ban users from this room."));
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to ban users from this room."));
|
||||||
return QString();
|
return QString();
|
||||||
@@ -485,18 +467,16 @@ QVector<ActionsModel::Action> actions{
|
|||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto plEvent = room->getCurrentState<RoomPowerLevelsEvent>();
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
|
if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to unban users from this room."));
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to unban users from this room."));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
auto state = room->currentState().get<RoomMemberEvent>(text);
|
auto state = room->currentState().get<RoomMemberEvent>(text);
|
||||||
if (state && state->membership() != Membership::Ban) {
|
if (state && state->membership() != Membership::Ban) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
room->unban(text);
|
room->unban(text);
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
|
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
|
||||||
|
|
||||||
@@ -523,13 +503,11 @@ QVector<ActionsModel::Action> actions{
|
|||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You cannot kick yourself from the room."));
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You cannot kick yourself from the room."));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
if (!room->isMember(parts[0])) {
|
if (!room->isMember(parts[0])) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#endif
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
auto plEvent = room->getCurrentState<RoomPowerLevelsEvent>();
|
|
||||||
auto kick = plEvent->kick();
|
auto kick = plEvent->kick();
|
||||||
if (plEvent->powerLevelForUser(room->localUser()->id()) < kick) {
|
if (plEvent->powerLevelForUser(room->localUser()->id()) < kick) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to kick users from this room."));
|
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to kick users from this room."));
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(source_parent);
|
Q_UNUSED(source_parent);
|
||||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
|
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
||||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
|||||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||||
uniqueAuthors.append(nextAuthor);
|
uniqueAuthors.append(nextAuthor);
|
||||||
}
|
}
|
||||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
!= MessageEventModel::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
|
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||||
) {
|
) {
|
||||||
@@ -105,7 +105,7 @@ QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
|||||||
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||||
};
|
};
|
||||||
stateEvents.append(nextState);
|
stateEvents.append(nextState);
|
||||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
!= MessageEventModel::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
|
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||||
) {
|
) {
|
||||||
@@ -123,7 +123,7 @@ QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
|
|||||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||||
uniqueAuthors.append(nextAvatar);
|
uniqueAuthors.append(nextAvatar);
|
||||||
}
|
}
|
||||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
!= MessageEventModel::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
|
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -11,12 +11,6 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#define running isJobPending
|
|
||||||
#else
|
|
||||||
#define running isJobRunning
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void CustomEmojiModel::fetchEmojis()
|
void CustomEmojiModel::fetchEmojis()
|
||||||
{
|
{
|
||||||
if (!Controller::instance().activeConnection()) {
|
if (!Controller::instance().activeConnection()) {
|
||||||
@@ -57,18 +51,12 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
|||||||
|
|
||||||
auto job = Controller::instance().activeConnection()->uploadFile(location.toLocalFile());
|
auto job = Controller::instance().activeConnection()->uploadFile(location.toLocalFile());
|
||||||
|
|
||||||
if (running(job)) {
|
if (isJobPending(job)) {
|
||||||
connect(job, &BaseJob::success, this, [this, name, job] {
|
connect(job, &BaseJob::success, this, [name, job] {
|
||||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes");
|
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes");
|
||||||
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
||||||
auto emojiData = json["images"].toObject();
|
auto emojiData = json["images"].toObject();
|
||||||
emojiData[QStringLiteral("%1").arg(name)] = QJsonObject({
|
emojiData[QStringLiteral("%1").arg(name)] = QJsonObject({{QStringLiteral("url"), job->contentUri().toString()}});
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
{QStringLiteral("url"), job->contentUri().toString()}
|
|
||||||
#else
|
|
||||||
{QStringLiteral("url"), job->contentUri()}
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
json["images"] = emojiData;
|
json["images"] = emojiData;
|
||||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes", json);
|
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes", json);
|
||||||
});
|
});
|
||||||
@@ -141,8 +129,9 @@ QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
|
|||||||
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
|
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
|
||||||
case Roles::MxcUrl:
|
case Roles::MxcUrl:
|
||||||
return data.url.mid(6);
|
return data.url.mid(6);
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ QHash<int, QByteArray> DevicesModel::roleNames() const
|
|||||||
|
|
||||||
void DevicesModel::logout(int index, const QString &password)
|
void DevicesModel::logout(int index, const QString &password)
|
||||||
{
|
{
|
||||||
auto job = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
|
auto job = Controller::instance().activeConnection()->callApi<DeleteDeviceJob>(m_devices[index].deviceId);
|
||||||
|
|
||||||
connect(job, &BaseJob::result, this, [this, job, password, index] {
|
connect(job, &BaseJob::result, this, [this, job, password, index] {
|
||||||
auto onSuccess = [this, index]() {
|
auto onSuccess = [this, index]() {
|
||||||
|
|||||||
@@ -11,12 +11,10 @@
|
|||||||
#include <events/roomavatarevent.h>
|
#include <events/roomavatarevent.h>
|
||||||
#include <events/roommemberevent.h>
|
#include <events/roommemberevent.h>
|
||||||
#include <events/simplestateevents.h>
|
#include <events/simplestateevents.h>
|
||||||
|
#include <events/stickerevent.h>
|
||||||
#include <user.h>
|
#include <user.h>
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include "pollevent.h"
|
#include "pollevent.h"
|
||||||
#endif
|
|
||||||
#include "stickerevent.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
@@ -27,14 +25,13 @@
|
|||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
QHash<int, QByteArray> MessageEventModel::roleNames() const
|
QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||||
roles[EventTypeRole] = "eventType";
|
roles[DelegateTypeRole] = "delegateType";
|
||||||
roles[MessageRole] = "message";
|
roles[MessageRole] = "message";
|
||||||
roles[EventIdRole] = "eventId";
|
roles[EventIdRole] = "eventId";
|
||||||
roles[TimeRole] = "time";
|
roles[TimeRole] = "time";
|
||||||
@@ -46,16 +43,16 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[SpecialMarksRole] = "marks";
|
roles[SpecialMarksRole] = "marks";
|
||||||
roles[LongOperationRole] = "progressInfo";
|
roles[LongOperationRole] = "progressInfo";
|
||||||
roles[FileMimetypeIcon] = "fileMimetypeIcon";
|
roles[FileMimetypeIcon] = "fileMimetypeIcon";
|
||||||
roles[AnnotationRole] = "annotation";
|
|
||||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||||
roles[IsReplyRole] = "isReply";
|
roles[IsReplyRole] = "isReply";
|
||||||
roles[ReplyRole] = "reply";
|
roles[ReplyRole] = "reply";
|
||||||
roles[ReplyIdRole] = "replyId";
|
roles[ReplyIdRole] = "replyId";
|
||||||
roles[UserMarkerRole] = "userMarker";
|
|
||||||
roles[ShowAuthorRole] = "showAuthor";
|
roles[ShowAuthorRole] = "showAuthor";
|
||||||
roles[ShowSectionRole] = "showSection";
|
roles[ShowSectionRole] = "showSection";
|
||||||
|
roles[ReadMarkersRole] = "readMarkers";
|
||||||
|
roles[ReadMarkersStringRole] = "readMarkersString";
|
||||||
|
roles[ShowReadMarkersRole] = "showReadMarkers";
|
||||||
roles[ReactionRole] = "reaction";
|
roles[ReactionRole] = "reaction";
|
||||||
roles[IsEditedRole] = "isEdited";
|
|
||||||
roles[SourceRole] = "source";
|
roles[SourceRole] = "source";
|
||||||
roles[MimeTypeRole] = "mimeType";
|
roles[MimeTypeRole] = "mimeType";
|
||||||
roles[FormattedBodyRole] = "formattedBody";
|
roles[FormattedBodyRole] = "formattedBody";
|
||||||
@@ -64,17 +61,17 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[VerifiedRole] = "verified";
|
roles[VerifiedRole] = "verified";
|
||||||
roles[DisplayNameForInitialsRole] = "displayNameForInitials";
|
roles[DisplayNameForInitialsRole] = "displayNameForInitials";
|
||||||
roles[AuthorDisplayNameRole] = "authorDisplayName";
|
roles[AuthorDisplayNameRole] = "authorDisplayName";
|
||||||
roles[IsNameChangeRole] = "isNameChange";
|
|
||||||
roles[IsAvatarChangeRole] = "isAvatarChange";
|
|
||||||
roles[IsRedactedRole] = "isRedacted";
|
roles[IsRedactedRole] = "isRedacted";
|
||||||
roles[GenericDisplayRole] = "genericDisplay";
|
roles[GenericDisplayRole] = "genericDisplay";
|
||||||
roles[IsPendingRole] = "isPending";
|
roles[IsPendingRole] = "isPending";
|
||||||
|
roles[LatitudeRole] = "latitude";
|
||||||
|
roles[LongitudeRole] = "longitude";
|
||||||
|
roles[AssetRole] = "asset";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEventModel::MessageEventModel(QObject *parent)
|
MessageEventModel::MessageEventModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, m_currentRoom(nullptr)
|
|
||||||
{
|
{
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
qmlRegisterAnonymousType<FileTransferInfo>("org.kde.neochat", 1);
|
qmlRegisterAnonymousType<FileTransferInfo>("org.kde.neochat", 1);
|
||||||
@@ -87,6 +84,11 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
|||||||
|
|
||||||
MessageEventModel::~MessageEventModel() = default;
|
MessageEventModel::~MessageEventModel() = default;
|
||||||
|
|
||||||
|
NeoChatRoom *MessageEventModel::room() const
|
||||||
|
{
|
||||||
|
return m_currentRoom;
|
||||||
|
}
|
||||||
|
|
||||||
void MessageEventModel::setRoom(NeoChatRoom *room)
|
void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||||
{
|
{
|
||||||
if (room == m_currentRoom) {
|
if (room == m_currentRoom) {
|
||||||
@@ -105,11 +107,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
|
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
|
||||||
room->getPreviousContent(50);
|
room->getPreviousContent(50);
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
lastReadEventId = room->lastFullyReadEventId();
|
lastReadEventId = room->lastFullyReadEventId();
|
||||||
#else
|
|
||||||
lastReadEventId = room->readMarkerEventId();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
|
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
|
||||||
@@ -158,11 +156,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
endInsertRows();
|
endInsertRows();
|
||||||
if (!m_lastReadEventIndex.isValid()) {
|
if (!m_lastReadEventIndex.isValid()) {
|
||||||
// no read marker, so see if we need to create one.
|
// no read marker, so see if we need to create one.
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
moveReadMarker(m_currentRoom->lastFullyReadEventId());
|
moveReadMarker(m_currentRoom->lastFullyReadEventId());
|
||||||
#else
|
|
||||||
moveReadMarker(m_currentRoom->readMarkerEventId());
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
if (biggest < m_currentRoom->maxTimelineIndex()) {
|
if (biggest < m_currentRoom->maxTimelineIndex()) {
|
||||||
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
|
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
|
||||||
@@ -203,7 +197,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
beginRemoveRows({}, i, i);
|
beginRemoveRows({}, i, i);
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows);
|
connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows);
|
||||||
connect(m_currentRoom, &Room::readMarkerMoved, this, [this](const QString &fromEventId, const QString &toEventId) {
|
connect(m_currentRoom, &Room::fullyReadMarkerMoved, this, [this](const QString &fromEventId, const QString &toEventId) {
|
||||||
Q_UNUSED(fromEventId);
|
Q_UNUSED(fromEventId);
|
||||||
moveReadMarker(toEventId);
|
moveReadMarker(toEventId);
|
||||||
});
|
});
|
||||||
@@ -216,13 +210,16 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
}
|
}
|
||||||
refreshEventRoles(eventId, {ReactionRole, Qt::DisplayRole});
|
refreshEventRoles(eventId, {ReactionRole, Qt::DisplayRole});
|
||||||
});
|
});
|
||||||
|
connect(m_currentRoom, &Room::changed, this, [this]() {
|
||||||
|
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
||||||
|
auto event = it->event();
|
||||||
|
refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole});
|
||||||
|
}
|
||||||
|
});
|
||||||
connect(m_currentRoom, &Room::newFileTransfer, this, &MessageEventModel::refreshEvent);
|
connect(m_currentRoom, &Room::newFileTransfer, this, &MessageEventModel::refreshEvent);
|
||||||
connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent);
|
connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent);
|
||||||
connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent);
|
connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent);
|
||||||
connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent);
|
connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent);
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
connect(m_currentRoom, &Room::fileTransferCancelled, this, &MessageEventModel::refreshEvent);
|
|
||||||
#endif
|
|
||||||
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [this] {
|
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [this] {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
@@ -308,7 +305,7 @@ int MessageEventModel::refreshEventRoles(const QString &id, const QVector<int> &
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
||||||
if (data(index(row, 0), EventTypeRole).toInt() == ReadMarker || data(index(row, 0), EventTypeRole).toInt() == Other) {
|
if (data(index(row, 0), DelegateTypeRole).toInt() == ReadMarker || data(index(row, 0), DelegateTypeRole).toInt() == Other) {
|
||||||
row++;
|
row++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,7 +436,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
|
|
||||||
if (m_lastReadEventIndex.row() == row) {
|
if (m_lastReadEventIndex.row() == row) {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case EventTypeRole:
|
case DelegateTypeRole:
|
||||||
return DelegateType::ReadMarker;
|
return DelegateType::ReadMarker;
|
||||||
case TimeRole: {
|
case TimeRole: {
|
||||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
|
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
|
||||||
@@ -488,10 +485,10 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == SourceRole) {
|
if (role == SourceRole) {
|
||||||
return evt.originalJson();
|
return QJsonDocument(evt.fullJson()).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == EventTypeRole) {
|
if (role == DelegateTypeRole) {
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
switch (e->msgtype()) {
|
switch (e->msgtype()) {
|
||||||
case MessageEventType::Emote:
|
case MessageEventType::Emote:
|
||||||
@@ -504,6 +501,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return DelegateType::Audio;
|
return DelegateType::Audio;
|
||||||
case MessageEventType::Video:
|
case MessageEventType::Video:
|
||||||
return DelegateType::Video;
|
return DelegateType::Video;
|
||||||
|
case MessageEventType::Location:
|
||||||
|
return DelegateType::Location;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -522,20 +521,18 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
if (is<const EncryptedEvent>(evt)) {
|
if (is<const EncryptedEvent>(evt)) {
|
||||||
return DelegateType::Encrypted;
|
return DelegateType::Encrypted;
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
if (is<PollStartEvent>(evt)) {
|
if (is<PollStartEvent>(evt)) {
|
||||||
if (evt.isRedacted()) {
|
if (evt.isRedacted()) {
|
||||||
return DelegateType::Message;
|
return DelegateType::Message;
|
||||||
}
|
}
|
||||||
return DelegateType::Poll;
|
return DelegateType::Poll;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
return DelegateType::Other;
|
return DelegateType::Other;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == EventResolvedTypeRole) {
|
if (role == EventResolvedTypeRole) {
|
||||||
return EventTypeRegistry::getMatrixType(evt.type());
|
return evt.type();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == AuthorRole) {
|
if (role == AuthorRole) {
|
||||||
@@ -558,6 +555,9 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
|
if (e->msgtype() == Quotient::MessageEventType::Location) {
|
||||||
|
return e->contentJson();
|
||||||
|
}
|
||||||
// Cannot use e.contentJson() here because some
|
// Cannot use e.contentJson() here because some
|
||||||
// EventContent classes inject values into the copy of the
|
// EventContent classes inject values into the copy of the
|
||||||
// content JSON stored in EventContent::Base
|
// content JSON stored in EventContent::Base
|
||||||
@@ -599,12 +599,25 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
|
|
||||||
if (role == SpecialMarksRole) {
|
if (role == SpecialMarksRole) {
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
|
// A pending event with an m.new_content key will be merged into the
|
||||||
|
// original event so don't show.
|
||||||
|
if (evt.contentJson().contains("m.new_content")) {
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
}
|
||||||
return pendingIt->deliveryStatus();
|
return pendingIt->deliveryStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *memberEvent = timelineIt->viewAs<RoomMemberEvent>();
|
if (evt.isStateEvent() && !NeoChatConfig::self()->showStateEvent()) {
|
||||||
if (memberEvent) {
|
return EventStatus::Hidden;
|
||||||
if ((memberEvent->isJoin() || memberEvent->isLeave()) && !NeoChatConfig::self()->showLeaveJoinEvent()) {
|
}
|
||||||
|
|
||||||
|
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(&evt)) {
|
||||||
|
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::self()->showLeaveJoinEvent()) {
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::self()->showRename()) {
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave()
|
||||||
|
&& !NeoChatConfig::self()->showAvatarUpdate()) {
|
||||||
return EventStatus::Hidden;
|
return EventStatus::Hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -618,7 +631,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return EventStatus::Hidden;
|
return EventStatus::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.isStateEvent() && static_cast<const StateEventBase &>(evt).repeatsState()) {
|
if (evt.isStateEvent() && static_cast<const StateEvent &>(evt).repeatsState()) {
|
||||||
return EventStatus::Hidden;
|
return EventStatus::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,14 +648,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return EventStatus::Normal;
|
return EventStatus::Normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == IsEditedRole) {
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
|
||||||
return !e->unsignedJson().isEmpty() && e->unsignedJson().contains("m.relations")
|
|
||||||
&& e->unsignedJson()["m.relations"].toObject().contains("m.replace");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == EventIdRole) {
|
if (role == EventIdRole) {
|
||||||
return !evt.id().isEmpty() ? evt.id() : evt.transactionId();
|
return !evt.id().isEmpty() ? evt.id() : evt.transactionId();
|
||||||
}
|
}
|
||||||
@@ -658,29 +663,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == AnnotationRole) {
|
|
||||||
if (isPending) {
|
|
||||||
return pendingIt->annotation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == TimeRole || role == SectionRole) {
|
if (role == TimeRole || role == SectionRole) {
|
||||||
auto ts = isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
|
auto ts = isPending ? pendingIt->lastUpdated() : makeMessageTimestamp(timelineIt);
|
||||||
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == UserMarkerRole) {
|
|
||||||
QVariantList variantList;
|
|
||||||
const auto users = m_currentRoom->usersAtEventId(evt.id());
|
|
||||||
for (User *user : users) {
|
|
||||||
if (user == m_currentRoom->localUser()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
variantList.append(QVariant::fromValue(user));
|
|
||||||
}
|
|
||||||
return variantList;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == IsReplyRole) {
|
if (role == IsReplyRole) {
|
||||||
return !evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString().isEmpty();
|
return !evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString().isEmpty();
|
||||||
}
|
}
|
||||||
@@ -766,7 +753,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
// While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
|
// While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
|
||||||
// See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
|
// See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
|
||||||
if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
|
if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
|
||||||
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, EventTypeRole) == MessageEventModel::State
|
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, DelegateTypeRole) == MessageEventModel::State
|
||||||
|| data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
|
|| data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
|
||||||
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day();
|
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day();
|
||||||
}
|
}
|
||||||
@@ -791,8 +778,70 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role == LatitudeRole) {
|
||||||
|
const auto geoUri = evt.contentJson()["geo_uri"_ls].toString();
|
||||||
|
if (geoUri.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto latitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[0];
|
||||||
|
return latitude.toFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == LongitudeRole) {
|
||||||
|
const auto geoUri = evt.contentJson()["geo_uri"_ls].toString();
|
||||||
|
if (geoUri.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto latitude = geoUri.split(u';')[0].split(u':')[1].split(u',')[1];
|
||||||
|
return latitude.toFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == AssetRole) {
|
||||||
|
const auto assetType = evt.contentJson()["org.matrix.msc3488.asset"].toObject()["type"].toString();
|
||||||
|
if (assetType.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return assetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ReadMarkersRole) {
|
||||||
|
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||||
|
userIds.remove(m_currentRoom->localUser()->id());
|
||||||
|
|
||||||
|
QVariantList users;
|
||||||
|
users.reserve(userIds.size());
|
||||||
|
for (const auto &userId : userIds) {
|
||||||
|
auto user = static_cast<NeoChatUser *>(m_currentRoom->user(userId));
|
||||||
|
users += userAtEvent(user, m_currentRoom, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ReadMarkersStringRole) {
|
||||||
|
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||||
|
userIds.remove(m_currentRoom->localUser()->id());
|
||||||
|
/**
|
||||||
|
* The string ends up in the form
|
||||||
|
* "x users: user1DisplayName, user2DisplayName, etc."
|
||||||
|
*/
|
||||||
|
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
|
||||||
|
for (const auto &userId : userIds) {
|
||||||
|
auto user = static_cast<NeoChatUser *>(m_currentRoom->user(userId));
|
||||||
|
readMarkersString += user->displayname(m_currentRoom) + i18nc("list separator", ", ");
|
||||||
|
}
|
||||||
|
readMarkersString.chop(2);
|
||||||
|
return readMarkersString;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ShowReadMarkersRole) {
|
||||||
|
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||||
|
userIds.remove(m_currentRoom->localUser()->id());
|
||||||
|
return userIds.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (role == ReactionRole) {
|
if (role == ReactionRole) {
|
||||||
const auto &annotations = m_currentRoom->relatedEvents(evt, EventRelation::Annotation());
|
const auto &annotations = m_currentRoom->relatedEvents(evt, EventRelation::AnnotationType);
|
||||||
if (annotations.isEmpty()) {
|
if (annotations.isEmpty()) {
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
@@ -802,7 +851,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (auto e = eventCast<const ReactionEvent>(a)) {
|
if (auto e = eventCast<const ReactionEvent>(a)) {
|
||||||
reactions[e->relation().key].append(static_cast<NeoChatUser *>(m_currentRoom->user(e->senderId())));
|
reactions[e->eventId()].append(static_cast<NeoChatUser *>(m_currentRoom->user(e->senderId())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -829,7 +878,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == MediaUrlRole) {
|
if (role == MediaUrlRole) {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
if (!e->hasFileContent()) {
|
if (!e->hasFileContent()) {
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -846,7 +894,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
// if (auto e = eventCast<const StickerEvent>(&evt)) {
|
// if (auto e = eventCast<const StickerEvent>(&evt)) {
|
||||||
// return m_currentRoom->makeMediaUrl(e->id(), e->url());
|
// return m_currentRoom->makeMediaUrl(e->id(), e->url());
|
||||||
// }
|
// }
|
||||||
#endif
|
|
||||||
|
|
||||||
// Construct link in the same form as urlToDownload as that function doesn't work for stickers
|
// Construct link in the same form as urlToDownload as that function doesn't work for stickers
|
||||||
if (auto e = eventCast<const StickerEvent>(&evt)) {
|
if (auto e = eventCast<const StickerEvent>(&evt)) {
|
||||||
@@ -861,14 +908,12 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == VerifiedRole) {
|
if (role == VerifiedRole) {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#ifdef Quotient_E2EE_ENABLED
|
#ifdef Quotient_E2EE_ENABLED
|
||||||
if (evt.originalEvent()) {
|
if (evt.originalEvent()) {
|
||||||
auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());
|
auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());
|
||||||
Q_ASSERT(encrypted);
|
Q_ASSERT(encrypted);
|
||||||
return m_currentRoom->connection()->isVerifiedSession(encrypted->sessionId().toLatin1());
|
return m_currentRoom->connection()->isVerifiedSession(encrypted->sessionId().toLatin1());
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -891,33 +936,18 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == IsNameChangeRole) {
|
|
||||||
auto roomMemberEvent = eventCast<const RoomMemberEvent>(&evt);
|
|
||||||
if (roomMemberEvent) {
|
|
||||||
return roomMemberEvent->isRename();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == IsAvatarChangeRole) {
|
|
||||||
auto roomMemberEvent = eventCast<const RoomMemberEvent>(&evt);
|
|
||||||
if (roomMemberEvent) {
|
|
||||||
return roomMemberEvent->isAvatarUpdate();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (role == IsRedactedRole) {
|
if (role == IsRedactedRole) {
|
||||||
return evt.isRedacted();
|
return evt.isRedacted();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == IsPendingRole) {
|
if (role == IsPendingRole) {
|
||||||
return row < m_currentRoom->pendingEvents().size();
|
return row < static_cast<int>(m_currentRoom->pendingEvents().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::eventIDToIndex(const QString &eventID) const
|
int MessageEventModel::eventIdToRow(const QString &eventID) const
|
||||||
{
|
{
|
||||||
const auto it = m_currentRoom->findInTimeline(eventID);
|
const auto it = m_currentRoom->findInTimeline(eventID);
|
||||||
if (it == m_currentRoom->historyEdge()) {
|
if (it == m_currentRoom->historyEdge()) {
|
||||||
@@ -959,7 +989,7 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
|
|||||||
targetMessage.insert("event_id", eventId);
|
targetMessage.insert("event_id", eventId);
|
||||||
targetMessage.insert("formattedBody", content["formatted_body"].toString());
|
targetMessage.insert("formattedBody", content["formatted_body"].toString());
|
||||||
// Need to get the message from the original eventId or body will have * on the front
|
// Need to get the message from the original eventId or body will have * on the front
|
||||||
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
QModelIndex idx = index(eventIdToRow(eventId), 0);
|
||||||
targetMessage.insert("message", idx.data(Qt::UserRole + 2));
|
targetMessage.insert("message", idx.data(Qt::UserRole + 2));
|
||||||
|
|
||||||
return targetMessage;
|
return targetMessage;
|
||||||
@@ -969,14 +999,14 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
|
|||||||
return targetMessage;
|
return targetMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline)
|
QVariant MessageEventModel::getLatestMessageFromRow(const int startRow)
|
||||||
{
|
{
|
||||||
QVariantMap replyResponse;
|
QVariantMap replyResponse;
|
||||||
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin() + baseline;
|
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin() + startRow;
|
||||||
|
|
||||||
// set a cap limit of baseline + 35 messages, to prevent loading a lot of messages
|
// set a cap limit of startRow + 35 messages, to prevent loading a lot of messages
|
||||||
// in rooms where the user has not sent many messages
|
// in rooms where the user has not sent many messages
|
||||||
const auto limit = timelineBottom + std::min(baseline + 35, m_currentRoom->timelineSize());
|
const auto limit = timelineBottom + std::min(startRow + 35, m_currentRoom->timelineSize());
|
||||||
|
|
||||||
for (auto it = timelineBottom; it != limit; ++it) {
|
for (auto it = timelineBottom; it != limit; ++it) {
|
||||||
auto evt = it->event();
|
auto evt = it->event();
|
||||||
@@ -998,7 +1028,7 @@ QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline)
|
|||||||
}
|
}
|
||||||
replyResponse.insert("event_id", eventId);
|
replyResponse.insert("event_id", eventId);
|
||||||
// Need to get the message from the original eventId or body will have * on the front
|
// Need to get the message from the original eventId or body will have * on the front
|
||||||
QModelIndex idx = index(eventIDToIndex(eventId), 0);
|
QModelIndex idx = index(eventIdToRow(eventId), 0);
|
||||||
replyResponse.insert("message", idx.data(Qt::UserRole + 2));
|
replyResponse.insert("message", idx.data(Qt::UserRole + 2));
|
||||||
replyResponse.insert("sender_id", QVariant::fromValue(m_currentRoom->getUser((*it)->senderId())));
|
replyResponse.insert("sender_id", QVariant::fromValue(m_currentRoom->getUser((*it)->senderId())));
|
||||||
replyResponse.insert("at", -it->index());
|
replyResponse.insert("at", -it->index());
|
||||||
|
|||||||
@@ -7,73 +7,99 @@
|
|||||||
|
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class MessageEventModel
|
||||||
|
*
|
||||||
|
* This class defines the model for visualising the room timeline.
|
||||||
|
*
|
||||||
|
* This model covers all event types in the timeline with many of the roles being
|
||||||
|
* specific to a subset of events. This means the user needs to understand which
|
||||||
|
* roles will return useful information for a given event type.
|
||||||
|
*
|
||||||
|
* @sa NeoChatRoom
|
||||||
|
*/
|
||||||
class MessageEventModel : public QAbstractListModel
|
class MessageEventModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current room that the model is getting its messages from.
|
||||||
|
*/
|
||||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief The type of delegate that is needed for the event.
|
||||||
|
*
|
||||||
|
* @note While similar this is not the matrix event or message type. This is
|
||||||
|
* to tell a QML ListView what delegate to show for each event. So while
|
||||||
|
* similar to the spec it is not the same.
|
||||||
|
*/
|
||||||
enum DelegateType {
|
enum DelegateType {
|
||||||
Emote,
|
Emote, /**< A message that begins with /me. */
|
||||||
Notice,
|
Notice, /**< A notice event. */
|
||||||
Image,
|
Image, /**< A message that is an image. */
|
||||||
Audio,
|
Audio, /**< A message that is an audio recording. */
|
||||||
Video,
|
Video, /**< A message that is a video. */
|
||||||
File,
|
File, /**< A message that is a file. */
|
||||||
Message,
|
Message, /**< A text message. */
|
||||||
Sticker,
|
Sticker, /**< A message that is a sticker. */
|
||||||
State,
|
State, /**< A state event in the room. */
|
||||||
Encrypted,
|
Encrypted, /**< An encrypted message that cannot be decrypted. */
|
||||||
ReadMarker,
|
ReadMarker, /**< The local user read marker. */
|
||||||
Poll,
|
Poll, /**< The initial event for a poll. */
|
||||||
Other,
|
Location, /**< A location event. */
|
||||||
|
Other, /**< Anything that cannot be classified as another type. */
|
||||||
};
|
};
|
||||||
Q_ENUM(DelegateType);
|
Q_ENUM(DelegateType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
EventTypeRole = Qt::UserRole + 1,
|
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
||||||
MessageRole,
|
MessageRole, /**< Plain text representation of the message. */
|
||||||
EventIdRole,
|
EventIdRole, /**< The matrix event ID of the event. */
|
||||||
TimeRole,
|
TimeRole, /**< The timestamp for when the event was sent. */
|
||||||
SectionRole,
|
SectionRole, /**< The date of the event as a string. */
|
||||||
AuthorRole,
|
AuthorRole, /**< The author of the event. */
|
||||||
ContentRole,
|
ContentRole, /**< The full message content. */
|
||||||
ContentTypeRole,
|
ContentTypeRole, /**< The content mime type. */
|
||||||
HighlightRole,
|
HighlightRole, /**< Whether the event should be highlighted. */
|
||||||
SpecialMarksRole,
|
SpecialMarksRole, /**< Whether the event is hidden or not. */
|
||||||
LongOperationRole,
|
LongOperationRole, /**< Progress info when downloading files. */
|
||||||
AnnotationRole,
|
FormattedBodyRole, /**< The formatted body of a rich message. */
|
||||||
UserMarkerRole,
|
GenericDisplayRole, /**< A generic string based upon the message type. */
|
||||||
FormattedBodyRole,
|
|
||||||
GenericDisplayRole,
|
|
||||||
|
|
||||||
MimeTypeRole,
|
MimeTypeRole, /**< The mime type of the message's file or media. */
|
||||||
FileMimetypeIcon,
|
FileMimetypeIcon, /**< The icon name for the mime type of a file. */
|
||||||
|
|
||||||
IsReplyRole,
|
IsReplyRole, /**< Is the message a reply to another event. */
|
||||||
ReplyRole,
|
ReplyRole, /**< The content data of the message that was replied to. */
|
||||||
ReplyIdRole,
|
ReplyIdRole, /**< The matrix ID of the message that was replied to. */
|
||||||
|
|
||||||
ShowAuthorRole,
|
ShowAuthorRole, /**< Whether the author's name should be shown. */
|
||||||
ShowSectionRole,
|
ShowSectionRole, /**< Whether the section header should be shown. */
|
||||||
|
|
||||||
ReactionRole,
|
ReadMarkersRole, /**< Other users at the event for read marker tracking. */
|
||||||
|
ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */
|
||||||
|
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
||||||
|
ReactionRole, /**< List of reactions to this event. */
|
||||||
|
SourceRole, /**< The full message source JSON. */
|
||||||
|
MediaUrlRole, /**< The source URL for any media in the message. */
|
||||||
|
|
||||||
IsEditedRole,
|
|
||||||
SourceRole,
|
|
||||||
MediaUrlRole,
|
|
||||||
// For debugging
|
// For debugging
|
||||||
EventResolvedTypeRole,
|
EventResolvedTypeRole, /**< The event type the message. */
|
||||||
AuthorIdRole,
|
AuthorIdRole, /**< Matrix ID of the message author. */
|
||||||
VerifiedRole,
|
|
||||||
// Sender's displayname, always without the matrix id
|
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
|
||||||
DisplayNameForInitialsRole,
|
DisplayNameForInitialsRole, /**< Sender's displayname, always without the matrix id. */
|
||||||
// The displayname for the event's sender; for name change events, the old displayname
|
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
|
||||||
AuthorDisplayNameRole,
|
IsRedactedRole, /**< Whether an event has been deleted. */
|
||||||
IsNameChangeRole,
|
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
|
||||||
IsAvatarChangeRole,
|
LatitudeRole, /**< Latitude for a location event. */
|
||||||
IsRedactedRole,
|
LongitudeRole, /**< Longitude for a location event. */
|
||||||
IsPendingRole,
|
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||||
LastRole, // Keep this last
|
LastRole, // Keep this last
|
||||||
};
|
};
|
||||||
Q_ENUM(EventRoles)
|
Q_ENUM(EventRoles)
|
||||||
@@ -81,20 +107,67 @@ public:
|
|||||||
explicit MessageEventModel(QObject *parent = nullptr);
|
explicit MessageEventModel(QObject *parent = nullptr);
|
||||||
~MessageEventModel() override;
|
~MessageEventModel() override;
|
||||||
|
|
||||||
[[nodiscard]] NeoChatRoom *room() const
|
[[nodiscard]] NeoChatRoom *room() const;
|
||||||
{
|
|
||||||
return m_currentRoom;
|
|
||||||
}
|
|
||||||
void setRoom(NeoChatRoom *room);
|
void setRoom(NeoChatRoom *room);
|
||||||
|
|
||||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] int eventIDToIndex(const QString &eventID) const;
|
/**
|
||||||
|
* @brief Get the row number of the given event ID in the model.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] int eventIdToRow(const QString &eventID) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the last message sent by the local user.
|
||||||
|
*
|
||||||
|
* @note This checks a maximum of the previous 35 message for performance reasons.
|
||||||
|
*
|
||||||
|
* @return a QVariantMap for the event with the following parameters:
|
||||||
|
* - eventId - The event ID.
|
||||||
|
* - formattedBody - The message text formatted as Qt::RichText.
|
||||||
|
* - message - The message text formatted as Qt::PlainText.
|
||||||
|
*/
|
||||||
Q_INVOKABLE [[nodiscard]] QVariant getLastLocalUserMessageEventId();
|
Q_INVOKABLE [[nodiscard]] QVariant getLastLocalUserMessageEventId();
|
||||||
Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromIndex(const int baseline);
|
|
||||||
Q_INVOKABLE void loadReply(const QModelIndex &row);
|
/**
|
||||||
|
* @brief Get the last message sent earlier than the given row.
|
||||||
|
*
|
||||||
|
* @note This checks a maximum of the previous 35 message for performance reasons.
|
||||||
|
*
|
||||||
|
* @return a QVariantMap for the event with the following parameters:
|
||||||
|
* - eventId - The event ID.
|
||||||
|
* - message - The message text formatted as Qt::PlainText.
|
||||||
|
* - sender_id - The matrix ID of the sender.
|
||||||
|
* - at - The QModelIndex of the message.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromRow(const int startRow);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load the event that the item at the given index replied to.
|
||||||
|
*
|
||||||
|
* This is used to ensure that the reply data is available when the message that
|
||||||
|
* was replied to is outside the currently loaded timeline.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void loadReply(const QModelIndex &index);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
int refreshEvent(const QString &eventId);
|
int refreshEvent(const QString &eventId);
|
||||||
|
|||||||
@@ -11,23 +11,20 @@ using namespace Quotient;
|
|||||||
MessageFilterModel::MessageFilterModel(QObject *parent)
|
MessageFilterModel::MessageFilterModel(QObject *parent)
|
||||||
: QSortFilterProxyModel(parent)
|
: QSortFilterProxyModel(parent)
|
||||||
{
|
{
|
||||||
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowStateEventChanged, this, [this] {
|
||||||
|
invalidateFilter();
|
||||||
|
});
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLeaveJoinEventChanged, this, [this] {
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLeaveJoinEventChanged, this, [this] {
|
||||||
beginResetModel();
|
invalidateFilter();
|
||||||
endResetModel();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowRenameChanged, this, [this] {
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowRenameChanged, this, [this] {
|
||||||
beginResetModel();
|
invalidateFilter();
|
||||||
endResetModel();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowAvatarUpdateChanged, this, [this] {
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowAvatarUpdateChanged, this, [this] {
|
||||||
beginResetModel();
|
invalidateFilter();
|
||||||
endResetModel();
|
|
||||||
});
|
});
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowDeletedMessagesChanged, this, [this] {
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowDeletedMessagesChanged, this, [this] {
|
||||||
beginResetModel();
|
invalidateFilter();
|
||||||
endResetModel();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,31 +32,20 @@ bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour
|
|||||||
{
|
{
|
||||||
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||||
|
|
||||||
const int specialMarks = index.data(MessageEventModel::SpecialMarksRole).toInt();
|
|
||||||
if (index.data(MessageEventModel::IsNameChangeRole).toBool() && !NeoChatConfig::self()->showRename()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index.data(MessageEventModel::IsAvatarChangeRole).toBool() && !NeoChatConfig::self()->showAvatarUpdate()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (index.data(MessageEventModel::IsRedactedRole).toBool() && !NeoChatConfig::self()->showDeletedMessages()) {
|
if (index.data(MessageEventModel::IsRedactedRole).toBool() && !NeoChatConfig::self()->showDeletedMessages()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int specialMarks = index.data(MessageEventModel::SpecialMarksRole).toInt();
|
||||||
if (specialMarks == EventStatus::Hidden || specialMarks == EventStatus::Replaced) {
|
if (specialMarks == EventStatus::Hidden || specialMarks == EventStatus::Replaced) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto eventType = index.data(MessageEventModel::EventTypeRole).toInt();
|
const auto eventType = index.data(MessageEventModel::DelegateTypeRole).toInt();
|
||||||
|
|
||||||
if (eventType == MessageEventModel::Other) {
|
if (eventType == MessageEventModel::Other) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!NeoChatConfig::self()->showLeaveJoinEvent() && eventType == MessageEventModel::State) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,11 +117,7 @@ void PublicRoomListModel::next(int count)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword, {}});
|
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword, {}});
|
||||||
#else
|
|
||||||
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword});
|
|
||||||
#endif
|
|
||||||
|
|
||||||
connect(job, &BaseJob::finished, this, [this] {
|
connect(job, &BaseJob::finished, this, [this] {
|
||||||
attempted = true;
|
attempted = true;
|
||||||
@@ -177,11 +173,7 @@ QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
if (avatarUrl.isEmpty()) {
|
if (avatarUrl.isEmpty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return avatarUrl.url().remove(0, 6);
|
return avatarUrl.url().remove(0, 6);
|
||||||
#else
|
|
||||||
return avatarUrl.remove(0, 6);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
if (role == TopicRole) {
|
if (role == TopicRole) {
|
||||||
return room.topic;
|
return room.topic;
|
||||||
|
|||||||
@@ -8,36 +8,25 @@
|
|||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "user.h"
|
#include "user.h"
|
||||||
|
#include <eventstats.h>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
#include <QDBusInterface>
|
#include <QDBusInterface>
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
#include "notificationsmanager.h"
|
|
||||||
#include <csapi/notifications.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(Quotient::JoinState)
|
Q_DECLARE_METATYPE(Quotient::JoinState)
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
bool useUnityCounter()
|
|
||||||
{
|
|
||||||
static const auto Result = QDBusInterface("com.canonical.Unity", "/").isValid();
|
|
||||||
|
|
||||||
return Result;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RoomListModel::RoomListModel(QObject *parent)
|
RoomListModel::RoomListModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
@@ -46,30 +35,32 @@ RoomListModel::RoomListModel(QObject *parent)
|
|||||||
m_categoryVisibility[collapsedSection] = false;
|
m_categoryVisibility[collapsedSection] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
connect(this, &RoomListModel::notificationCountChanged, this, [this]() {
|
connect(this, &RoomListModel::notificationCountChanged, this, [this]() {
|
||||||
if (useUnityCounter()) {
|
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||||
// copied from Telegram desktop
|
#ifndef Q_OS_ANDROID
|
||||||
const auto launcherUrl = "application://org.kde.neochat.desktop";
|
// copied from Telegram desktop
|
||||||
// Gnome requires that count is a 64bit integer
|
const auto launcherUrl = "application://org.kde.neochat.desktop";
|
||||||
const qint64 counterSlice = std::min(m_notificationCount, 9999);
|
// Gnome requires that count is a 64bit integer
|
||||||
QVariantMap dbusUnityProperties;
|
const qint64 counterSlice = std::min(m_notificationCount, 9999);
|
||||||
|
QVariantMap dbusUnityProperties;
|
||||||
|
|
||||||
if (counterSlice > 0) {
|
if (counterSlice > 0) {
|
||||||
dbusUnityProperties["count"] = counterSlice;
|
dbusUnityProperties["count"] = counterSlice;
|
||||||
dbusUnityProperties["count-visible"] = true;
|
dbusUnityProperties["count-visible"] = true;
|
||||||
} else {
|
} else {
|
||||||
dbusUnityProperties["count-visible"] = false;
|
dbusUnityProperties["count-visible"] = false;
|
||||||
}
|
|
||||||
|
|
||||||
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat", "com.canonical.Unity.LauncherEntry", "Update");
|
|
||||||
|
|
||||||
signal.setArguments({launcherUrl, dbusUnityProperties});
|
|
||||||
|
|
||||||
QDBusConnection::sessionBus().send(signal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat", "com.canonical.Unity.LauncherEntry", "Update");
|
||||||
|
|
||||||
|
signal.setArguments({launcherUrl, dbusUnityProperties});
|
||||||
|
|
||||||
|
QDBusConnection::sessionBus().send(signal);
|
||||||
|
#endif // Q_OS_ANDROID
|
||||||
|
#else
|
||||||
|
qGuiApp->setBadgeNumber(m_notificationCount);
|
||||||
|
#endif // QT_VERSION_CHECK(6, 6, 0)
|
||||||
});
|
});
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomListModel::~RoomListModel() = default;
|
RoomListModel::~RoomListModel() = default;
|
||||||
@@ -154,10 +145,10 @@ void RoomListModel::doAddRoom(Room *r)
|
|||||||
void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
||||||
{
|
{
|
||||||
connect(room, &Room::displaynameChanged, this, [this, room] {
|
connect(room, &Room::displaynameChanged, this, [this, room] {
|
||||||
refresh(room);
|
refresh(room, {DisplayNameRole, NameRole});
|
||||||
});
|
});
|
||||||
connect(room, &Room::unreadMessagesChanged, this, [this, room] {
|
connect(room, &Room::unreadStatsChanged, this, [this, room] {
|
||||||
refresh(room);
|
refresh(room, {UnreadCountRole, NotificationCountRole, HighlightCountRole});
|
||||||
});
|
});
|
||||||
connect(room, &Room::notificationCountChanged, this, [this, room] {
|
connect(room, &Room::notificationCountChanged, this, [this, room] {
|
||||||
refresh(room);
|
refresh(room);
|
||||||
@@ -172,97 +163,14 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
|||||||
refresh(room);
|
refresh(room);
|
||||||
});
|
});
|
||||||
connect(room, &Room::addedMessages, this, [this, room] {
|
connect(room, &Room::addedMessages, this, [this, room] {
|
||||||
refresh(room, {LastEventRole, SubtitleTextRole});
|
refresh(room, {LastEventRole, SubtitleTextRole, LastActiveTimeRole});
|
||||||
});
|
});
|
||||||
connect(room, &Room::pendingEventMerged, this, [this, room] {
|
connect(room, &Room::pendingEventMerged, this, [this, room] {
|
||||||
refresh(room, {LastEventRole, SubtitleTextRole});
|
refresh(room, {LastEventRole, SubtitleTextRole});
|
||||||
});
|
});
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
connect(room, &Room::notificationCountChanged, this, &RoomListModel::handleNotifications);
|
|
||||||
#endif
|
|
||||||
connect(room, &Room::highlightCountChanged, this, [this, room] {
|
|
||||||
if (room->highlightCount() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (room->timelineSize() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto *lastEvent = room->lastEvent();
|
|
||||||
|
|
||||||
if (!lastEvent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lastEvent->isStateEvent()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
User *sender = room->user(lastEvent->senderId());
|
|
||||||
if (sender == room->localUser()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Q_EMIT newHighlight(room->id(), lastEvent->id(), room->displayName(), sender->displayname(), room->eventToString(*lastEvent), room->avatar(128));
|
|
||||||
});
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
connect(room, &Room::notificationCountChanged, this, &RoomListModel::refreshNotificationCount);
|
|
||||||
#else
|
|
||||||
connect(room, &Room::unreadStatsChanged, this, &RoomListModel::refreshNotificationCount);
|
connect(room, &Room::unreadStatsChanged, this, &RoomListModel::refreshNotificationCount);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
void RoomListModel::handleNotifications()
|
|
||||||
{
|
|
||||||
static bool initial = true;
|
|
||||||
static QStringList oldNotifications;
|
|
||||||
auto job = m_connection->callApi<GetNotificationsJob>();
|
|
||||||
|
|
||||||
connect(job, &BaseJob::success, this, [this, job]() {
|
|
||||||
const auto notifications = job->jsonData()["notifications"].toArray();
|
|
||||||
if (initial) {
|
|
||||||
initial = false;
|
|
||||||
for (const auto &n : notifications) {
|
|
||||||
oldNotifications += n.toObject()["event"].toObject()["event_id"].toString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const auto &n : notifications) {
|
|
||||||
const auto notification = n.toObject();
|
|
||||||
if (notification["read"].toBool()) {
|
|
||||||
oldNotifications.removeOne(notification["event"].toObject()["event_id"].toString());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (oldNotifications.contains(notification["event"].toObject()["event_id"].toString())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
oldNotifications += notification["event"].toObject()["event_id"].toString();
|
|
||||||
|
|
||||||
auto room = m_connection->room(notification["room_id"].toString());
|
|
||||||
auto currentRoom = RoomManager::instance().currentRoom();
|
|
||||||
bool roomIsActive = currentRoom && room->id() == currentRoom->id();
|
|
||||||
|
|
||||||
// If room exists, room is NOT active OR the application is NOT active, show notification
|
|
||||||
if (room && !(roomIsActive && QGuiApplication::applicationState() == Qt::ApplicationActive)) {
|
|
||||||
// The room might have been deleted (for example rejected invitation).
|
|
||||||
auto sender = room->user(notification["event"].toObject()["sender"].toString());
|
|
||||||
|
|
||||||
QImage avatar_image;
|
|
||||||
if (!sender->avatarUrl(room).isEmpty()) {
|
|
||||||
avatar_image = sender->avatar(128, room);
|
|
||||||
} else {
|
|
||||||
avatar_image = room->avatar(128);
|
|
||||||
}
|
|
||||||
NotificationsManager::instance().postNotification(dynamic_cast<NeoChatRoom *>(room),
|
|
||||||
sender->displayname(room),
|
|
||||||
notification["event"].toObject()["content"].toObject()["body"].toString(),
|
|
||||||
avatar_image,
|
|
||||||
notification["event"].toObject()["event_id"].toString(),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void RoomListModel::refreshNotificationCount()
|
void RoomListModel::refreshNotificationCount()
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@@ -387,7 +295,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
return NeoChatRoomType::Normal;
|
return NeoChatRoomType::Normal;
|
||||||
}
|
}
|
||||||
if (role == UnreadCountRole) {
|
if (role == UnreadCountRole) {
|
||||||
return room->unreadCount();
|
return room->unreadStats().notableCount;
|
||||||
}
|
}
|
||||||
if (role == NotificationCountRole) {
|
if (role == NotificationCountRole) {
|
||||||
return room->notificationCount();
|
return room->notificationCount();
|
||||||
@@ -417,7 +325,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
return m_categoryVisibility.value(data(index, CategoryRole).toInt(), true);
|
return m_categoryVisibility.value(data(index, CategoryRole).toInt(), true);
|
||||||
}
|
}
|
||||||
if (role == SubtitleTextRole) {
|
if (role == SubtitleTextRole) {
|
||||||
return room->subtitleText();
|
return room->lastEventToString(Qt::PlainText, true);
|
||||||
}
|
}
|
||||||
if (role == AvatarImageRole) {
|
if (role == AvatarImageRole) {
|
||||||
return room->avatar(128);
|
return room->avatar(128);
|
||||||
|
|||||||
@@ -107,14 +107,10 @@ private:
|
|||||||
QString m_activeSpaceId = "";
|
QString m_activeSpaceId = "";
|
||||||
|
|
||||||
void connectRoomSignals(NeoChatRoom *room);
|
void connectRoomSignals(NeoChatRoom *room);
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
void handleNotifications();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
void notificationCountChanged();
|
void notificationCountChanged();
|
||||||
|
|
||||||
void roomAdded(NeoChatRoom *_t1);
|
void roomAdded(NeoChatRoom *_t1);
|
||||||
void newHighlight(const QString &_t1, const QString &_t2, const QString &_t3, const QString &_t4, const QString &_t5, const QImage &_t6);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <connection.h>
|
#include <connection.h>
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include <csapi/search.h>
|
#include <csapi/search.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
@@ -34,7 +32,6 @@ void SearchModel::setSearchText(const QString &searchText)
|
|||||||
|
|
||||||
void SearchModel::search()
|
void SearchModel::search()
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
Q_ASSERT(m_connection);
|
Q_ASSERT(m_connection);
|
||||||
setSearching(true);
|
setSearching(true);
|
||||||
if (m_job) {
|
if (m_job) {
|
||||||
@@ -43,20 +40,26 @@ void SearchModel::search()
|
|||||||
}
|
}
|
||||||
|
|
||||||
SearchJob::RoomEventsCriteria criteria{
|
SearchJob::RoomEventsCriteria criteria{
|
||||||
m_searchText,
|
.searchTerm = m_searchText,
|
||||||
{},
|
.keys = {},
|
||||||
RoomEventFilter{
|
.filter =
|
||||||
.rooms = {m_room->id()},
|
RoomEventFilter{
|
||||||
},
|
.unreadThreadNotifications = none,
|
||||||
"recent",
|
.lazyLoadMembers = true,
|
||||||
SearchJob::IncludeEventContext{3, 3, true},
|
.includeRedundantMembers = false,
|
||||||
false,
|
.notRooms = {},
|
||||||
none,
|
.rooms = {m_room->id()},
|
||||||
|
.containsUrl = false,
|
||||||
|
},
|
||||||
|
.orderBy = "recent",
|
||||||
|
.eventContext = SearchJob::IncludeEventContext{3, 3, true},
|
||||||
|
.includeState = false,
|
||||||
|
.groupings = none,
|
||||||
};
|
};
|
||||||
|
|
||||||
auto job = m_connection->callApi<SearchJob>(SearchJob::Categories{criteria});
|
auto job = m_connection->callApi<SearchJob>(SearchJob::Categories{criteria});
|
||||||
m_job = job;
|
m_job = job;
|
||||||
connect(job, &BaseJob::finished, this, [=] {
|
connect(job, &BaseJob::finished, this, [this, job] {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_result = job->searchCategories().roomEvents;
|
m_result = job->searchCategories().roomEvents;
|
||||||
endResetModel();
|
endResetModel();
|
||||||
@@ -64,7 +67,6 @@ void SearchModel::search()
|
|||||||
m_job = nullptr;
|
m_job = nullptr;
|
||||||
// TODO error handling
|
// TODO error handling
|
||||||
});
|
});
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connection *SearchModel::connection() const
|
Connection *SearchModel::connection() const
|
||||||
@@ -80,7 +82,6 @@ void SearchModel::setConnection(Connection *connection)
|
|||||||
|
|
||||||
QVariant SearchModel::data(const QModelIndex &index, int role) const
|
QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
auto row = index.row();
|
auto row = index.row();
|
||||||
const auto &event = *m_result->results[row].result;
|
const auto &event = *m_result->results[row].result;
|
||||||
switch (role) {
|
switch (role) {
|
||||||
@@ -110,17 +111,14 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
|||||||
return event.originTimestamp();
|
return event.originTimestamp();
|
||||||
}
|
}
|
||||||
return MessageEventModel::DelegateType::Message;
|
return MessageEventModel::DelegateType::Message;
|
||||||
#endif
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int SearchModel::rowCount(const QModelIndex &parent) const
|
int SearchModel::rowCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
Q_UNUSED(parent);
|
||||||
if (m_result.has_value()) {
|
if (m_result.has_value()) {
|
||||||
return m_result->results.size();
|
return m_result->results.size();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include <csapi/search.h>
|
#include <csapi/search.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace Quotient
|
namespace Quotient
|
||||||
{
|
{
|
||||||
@@ -68,10 +66,8 @@ private:
|
|||||||
QString m_searchText;
|
QString m_searchText;
|
||||||
Quotient::Connection *m_connection = nullptr;
|
Quotient::Connection *m_connection = nullptr;
|
||||||
NeoChatRoom *m_room = nullptr;
|
NeoChatRoom *m_room = nullptr;
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
|
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
|
||||||
Quotient::SearchJob *m_job = nullptr;
|
Quotient::SearchJob *m_job = nullptr;
|
||||||
#endif
|
|
||||||
bool m_searching = false;
|
bool m_searching = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -95,11 +95,7 @@ void ServerListModel::checkServer(const QString &url)
|
|||||||
KConfigGroup serverGroup(&dataResource, "Servers");
|
KConfigGroup serverGroup(&dataResource, "Servers");
|
||||||
|
|
||||||
if (!serverGroup.hasKey(url)) {
|
if (!serverGroup.hasKey(url)) {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
if (Quotient::isJobPending(m_checkServerJob)) {
|
if (Quotient::isJobPending(m_checkServerJob)) {
|
||||||
#else
|
|
||||||
if (Quotient::isJobRunning(m_checkServerJob)) {
|
|
||||||
#endif
|
|
||||||
m_checkServerJob->abandon();
|
m_checkServerJob->abandon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ QHash<int, QByteArray> StateModel::roleNames() const
|
|||||||
}
|
}
|
||||||
QVariant StateModel::data(const QModelIndex &index, int role) const
|
QVariant StateModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
auto row = index.row();
|
auto row = index.row();
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case TypeRole:
|
case TypeRole:
|
||||||
@@ -24,18 +23,13 @@ QVariant StateModel::data(const QModelIndex &index, int role) const
|
|||||||
case SourceRole:
|
case SourceRole:
|
||||||
return QJsonDocument(m_room->currentState().events()[m_room->currentState().events().keys()[row]]->fullJson()).toJson();
|
return QJsonDocument(m_room->currentState().events()[m_room->currentState().events().keys()[row]]->fullJson()).toJson();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int StateModel::rowCount(const QModelIndex &parent) const
|
int StateModel::rowCount(const QModelIndex &parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return m_room->currentState().events().size();
|
return m_room->currentState().events().size();
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NeoChatRoom *StateModel::room() const
|
NeoChatRoom *StateModel::room() const
|
||||||
@@ -49,7 +43,7 @@ void StateModel::setRoom(NeoChatRoom *room)
|
|||||||
Q_EMIT roomChanged();
|
Q_EMIT roomChanged();
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
connect(room, &NeoChatRoom::changed, this, [=] {
|
connect(room, &NeoChatRoom::changed, this, [this] {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -131,11 +131,7 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
|
|||||||
if (avatarUrl.isEmpty()) {
|
if (avatarUrl.isEmpty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return avatarUrl.url().remove(0, 6);
|
return avatarUrl.url().remove(0, 6);
|
||||||
#else
|
|
||||||
return avatarUrl.remove(0, 6);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
if (role == UserIDRole) {
|
if (role == UserIDRole) {
|
||||||
return user.userId;
|
return user.userId;
|
||||||
|
|||||||
@@ -44,13 +44,9 @@ void UserListModel::setRoom(NeoChatRoom *room)
|
|||||||
std::sort(m_users.begin(), m_users.end(), room->memberSorter());
|
std::sort(m_users.begin(), m_users.end(), room->memberSorter());
|
||||||
}
|
}
|
||||||
for (User *user : std::as_const(m_users)) {
|
for (User *user : std::as_const(m_users)) {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
||||||
avatarChanged(user, m_currentRoom);
|
avatarChanged(user, m_currentRoom);
|
||||||
});
|
});
|
||||||
#else
|
|
||||||
connect(user, &User::avatarChanged, this, &UserListModel::avatarChanged);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
||||||
setRoom(nullptr);
|
setRoom(nullptr);
|
||||||
@@ -96,15 +92,14 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
|||||||
return QVariant::fromValue(user);
|
return QVariant::fromValue(user);
|
||||||
}
|
}
|
||||||
if (role == PowerLevelRole) {
|
if (role == PowerLevelRole) {
|
||||||
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
|
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
|
||||||
|
if (!pl) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return pl->powerLevelForUser(user->id());
|
return pl->powerLevelForUser(user->id());
|
||||||
}
|
}
|
||||||
if (role == PowerLevelStringRole) {
|
if (role == PowerLevelStringRole) {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
|
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
|
||||||
#else
|
|
||||||
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
|
|
||||||
#endif
|
|
||||||
// User might not in the room yet, in this case pl can be nullptr.
|
// User might not in the room yet, in this case pl can be nullptr.
|
||||||
// e.g. When invited but user not accepted or denied the invitation.
|
// e.g. When invited but user not accepted or denied the invitation.
|
||||||
if (!pl) {
|
if (!pl) {
|
||||||
@@ -143,13 +138,9 @@ void UserListModel::userAdded(Quotient::User *user)
|
|||||||
beginInsertRows(QModelIndex(), pos, pos);
|
beginInsertRows(QModelIndex(), pos, pos);
|
||||||
m_users.insert(pos, user);
|
m_users.insert(pos, user);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
||||||
avatarChanged(user, m_currentRoom);
|
avatarChanged(user, m_currentRoom);
|
||||||
});
|
});
|
||||||
#else
|
|
||||||
connect(user, &Quotient::User::avatarChanged, this, &UserListModel::avatarChanged);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UserListModel::userRemoved(Quotient::User *user)
|
void UserListModel::userRemoved(Quotient::User *user)
|
||||||
@@ -188,13 +179,9 @@ void UserListModel::refreshAll()
|
|||||||
std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter());
|
std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter());
|
||||||
}
|
}
|
||||||
for (User *user : std::as_const(m_users)) {
|
for (User *user : std::as_const(m_users)) {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
||||||
avatarChanged(user, m_currentRoom);
|
avatarChanged(user, m_currentRoom);
|
||||||
});
|
});
|
||||||
#else
|
|
||||||
connect(user, &User::avatarChanged, this, &UserListModel::avatarChanged);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
||||||
setRoom(nullptr);
|
setRoom(nullptr);
|
||||||
@@ -217,6 +204,9 @@ int UserListModel::findUserPos(Quotient::User *user) const
|
|||||||
|
|
||||||
int UserListModel::findUserPos(const QString &username) const
|
int UserListModel::findUserPos(const QString &username) const
|
||||||
{
|
{
|
||||||
|
if (!m_currentRoom) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username);
|
return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Kitsune Ral <Kitsune-Ral@users.sf.net>
|
|
||||||
// SPDX-FileCopyrightText: Tobias Fella <tobias.fella@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
|
|
||||||
#include "neochataccountregistry.h"
|
|
||||||
|
|
||||||
#include <connection.h>
|
|
||||||
|
|
||||||
using namespace Quotient;
|
|
||||||
|
|
||||||
void AccountRegistry::add(Connection *c)
|
|
||||||
{
|
|
||||||
if (m_accounts.contains(c))
|
|
||||||
return;
|
|
||||||
beginInsertRows(QModelIndex(), m_accounts.size(), m_accounts.size());
|
|
||||||
m_accounts += c;
|
|
||||||
endInsertRows();
|
|
||||||
emit accountCountChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccountRegistry::drop(Connection *c)
|
|
||||||
{
|
|
||||||
beginRemoveRows(QModelIndex(), m_accounts.indexOf(c), m_accounts.indexOf(c));
|
|
||||||
m_accounts.removeOne(c);
|
|
||||||
endRemoveRows();
|
|
||||||
Q_ASSERT(!m_accounts.contains(c));
|
|
||||||
emit accountCountChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AccountRegistry::isLoggedIn(const QString &userId) const
|
|
||||||
{
|
|
||||||
return std::any_of(m_accounts.cbegin(), m_accounts.cend(), [&userId](Connection *a) {
|
|
||||||
return a->userId() == userId;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AccountRegistry::contains(Connection *c) const
|
|
||||||
{
|
|
||||||
return m_accounts.contains(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountRegistry::AccountRegistry() = default;
|
|
||||||
|
|
||||||
QVariant AccountRegistry::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index.row() >= m_accounts.count()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto account = m_accounts[index.row()];
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case ConnectionRole:
|
|
||||||
return QVariant::fromValue(account);
|
|
||||||
case UserIdRole:
|
|
||||||
return QVariant::fromValue(account->userId());
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
int AccountRegistry::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
if (parent.isValid()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_accounts.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> AccountRegistry::roleNames() const
|
|
||||||
{
|
|
||||||
return {{ConnectionRole, "connection"}, {UserIdRole, "userId"}};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AccountRegistry::isEmpty() const
|
|
||||||
{
|
|
||||||
return m_accounts.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
int AccountRegistry::count() const
|
|
||||||
{
|
|
||||||
return m_accounts.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
const QVector<Connection *> AccountRegistry::accounts() const
|
|
||||||
{
|
|
||||||
return m_accounts;
|
|
||||||
}
|
|
||||||
|
|
||||||
Connection *AccountRegistry::get(const QString &userId)
|
|
||||||
{
|
|
||||||
for (const auto &connection : m_accounts) {
|
|
||||||
if (connection->userId() == userId) {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2020 Kitsune Ral <Kitsune-Ral@users.sf.net>
|
|
||||||
// SPDX-FileCopyrightText: Tobias Fella <tobias.fella@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QList>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
namespace Quotient
|
|
||||||
{
|
|
||||||
class Connection;
|
|
||||||
|
|
||||||
class AccountRegistry : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(int accountCount READ count NOTIFY accountCountChanged);
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum EventRoles {
|
|
||||||
ConnectionRole = Qt::UserRole + 1,
|
|
||||||
UserIdRole = Qt::DisplayRole,
|
|
||||||
};
|
|
||||||
|
|
||||||
static AccountRegistry &instance()
|
|
||||||
{
|
|
||||||
static AccountRegistry _instance;
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QVector<Connection *> accounts() const;
|
|
||||||
void add(Connection *a);
|
|
||||||
void drop(Connection *a);
|
|
||||||
bool isLoggedIn(const QString &userId) const;
|
|
||||||
bool isEmpty() const;
|
|
||||||
int count() const;
|
|
||||||
bool contains(Connection *) const;
|
|
||||||
Connection *get(const QString &userId);
|
|
||||||
|
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
|
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void accountCountChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
AccountRegistry();
|
|
||||||
|
|
||||||
QVector<Connection *> m_accounts;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -29,10 +29,6 @@
|
|||||||
<label>Merge Room Lists</label>
|
<label>Merge Room Lists</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="ShowLeaveJoinEvent" type="bool">
|
|
||||||
<label>Show leave and join events in the timeline</label>
|
|
||||||
<default>true</default>
|
|
||||||
</entry>
|
|
||||||
<entry name="AllowQuickEdit" type="bool">
|
<entry name="AllowQuickEdit" type="bool">
|
||||||
<label>Use s/text/replacement syntax to edit your last message.</label>
|
<label>Use s/text/replacement syntax to edit your last message.</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
@@ -72,6 +68,14 @@
|
|||||||
<label>Use a compact room list layout</label>
|
<label>Use a compact room list layout</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry name="ShowStateEvent" type="bool">
|
||||||
|
<label>Show state events in the timeline</label>
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="ShowLeaveJoinEvent" type="bool">
|
||||||
|
<label>Show leave and join events in the timeline</label>
|
||||||
|
<default>true</default>
|
||||||
|
</entry>
|
||||||
<entry name="ShowRename" type="bool">
|
<entry name="ShowRename" type="bool">
|
||||||
<label>Show rename events in the timeline</label>
|
<label>Show rename events in the timeline</label>
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
|
|||||||
@@ -8,14 +8,15 @@
|
|||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include <QMediaMetaData>
|
#include <QMediaMetaData>
|
||||||
#include <QMediaPlayer>
|
#include <QMediaPlayer>
|
||||||
|
|
||||||
|
#include <jobs/basejob.h>
|
||||||
#include <qcoro/qcorosignal.h>
|
#include <qcoro/qcorosignal.h>
|
||||||
|
|
||||||
#include <connection.h>
|
#include <connection.h>
|
||||||
|
#include <csapi/account-data.h>
|
||||||
#include <csapi/directory.h>
|
#include <csapi/directory.h>
|
||||||
#include <csapi/pushrules.h>
|
#include <csapi/pushrules.h>
|
||||||
#include <csapi/redaction.h>
|
#include <csapi/redaction.h>
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
#include <csapi/room_state.h>
|
#include <csapi/room_state.h>
|
||||||
#include <csapi/typing.h>
|
#include <csapi/typing.h>
|
||||||
#include <events/encryptionevent.h>
|
#include <events/encryptionevent.h>
|
||||||
|
#include <events/eventrelation.h>
|
||||||
#include <events/reactionevent.h>
|
#include <events/reactionevent.h>
|
||||||
#include <events/redactionevent.h>
|
#include <events/redactionevent.h>
|
||||||
#include <events/roomavatarevent.h>
|
#include <events/roomavatarevent.h>
|
||||||
@@ -30,24 +32,20 @@
|
|||||||
#include <events/roommemberevent.h>
|
#include <events/roommemberevent.h>
|
||||||
#include <events/roompowerlevelsevent.h>
|
#include <events/roompowerlevelsevent.h>
|
||||||
#include <events/simplestateevents.h>
|
#include <events/simplestateevents.h>
|
||||||
|
#include <events/stickerevent.h>
|
||||||
|
#include <eventstats.h>
|
||||||
#include <jobs/downloadfilejob.h>
|
#include <jobs/downloadfilejob.h>
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
#include <joinstate.h>
|
|
||||||
#endif
|
|
||||||
#include <qt_connection_util.h>
|
#include <qt_connection_util.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
#include "filetransferpseudojob.h"
|
||||||
#include "joinrulesevent.h"
|
#include "joinrulesevent.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "notificationsmanager.h"
|
#include "notificationsmanager.h"
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include "pollevent.h"
|
#include "pollevent.h"
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
#endif
|
#include "texthandler.h"
|
||||||
#include "filetransferpseudojob.h"
|
|
||||||
#include "stickerevent.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
#include <KIO/Job>
|
#include <KIO/Job>
|
||||||
@@ -84,7 +82,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
if (this->joinState() != JoinState::Invite) {
|
if (this->joinState() != JoinState::Invite) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const QString senderId = getCurrentState<RoomMemberEvent>(localUser()->id())->senderId();
|
const QString senderId = currentState().get<RoomMemberEvent>(localUser()->id())->senderId();
|
||||||
QImage avatar_image;
|
QImage avatar_image;
|
||||||
if (!user(senderId)->avatarUrl(this).isEmpty()) {
|
if (!user(senderId)->avatarUrl(this).isEmpty()) {
|
||||||
avatar_image = user(senderId)->avatar(128, this);
|
avatar_image = user(senderId)->avatar(128, this);
|
||||||
@@ -98,6 +96,42 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
Q_EMIT canEncryptRoomChanged();
|
Q_EMIT canEncryptRoomChanged();
|
||||||
});
|
});
|
||||||
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
||||||
|
connect(this, &Room::changed, this, [this]() {
|
||||||
|
Q_EMIT defaultUrlPreviewStateChanged();
|
||||||
|
});
|
||||||
|
connect(this, &Room::accountDataChanged, this, [this](QString type) {
|
||||||
|
if (type == "org.matrix.room.preview_urls") {
|
||||||
|
Q_EMIT urlPreviewEnabledChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeoChatRoom::hasFileUploading() const
|
||||||
|
{
|
||||||
|
return m_hasFileUploading;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setHasFileUploading(bool value)
|
||||||
|
{
|
||||||
|
if (value == m_hasFileUploading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_hasFileUploading = value;
|
||||||
|
Q_EMIT hasFileUploadingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::fileUploadingProgress() const
|
||||||
|
{
|
||||||
|
return m_fileUploadingProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setFileUploadingProgress(int value)
|
||||||
|
{
|
||||||
|
if (m_fileUploadingProgress == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_fileUploadingProgress = value;
|
||||||
|
Q_EMIT fileUploadingProgressChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
|
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
|
||||||
@@ -137,17 +171,9 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
|
|||||||
} else {
|
} else {
|
||||||
content = new EventContent::FileContent(url, fileInfo.size(), mime, fileInfo.fileName());
|
content = new EventContent::FileContent(url, fileInfo.size(), mime, fileInfo.fileName());
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, content);
|
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, content);
|
||||||
#else
|
|
||||||
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, url, false);
|
|
||||||
#endif
|
|
||||||
setHasFileUploading(true);
|
setHasFileUploading(true);
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
connect(this, &Room::fileTransferCompleted, [this, txnId](const QString &id, FileSourceInfo) {
|
connect(this, &Room::fileTransferCompleted, [this, txnId](const QString &id, FileSourceInfo) {
|
||||||
#else
|
|
||||||
connect(this, &Room::fileTransferCompleted, [this, txnId](const QString &id, const QUrl & /*localFile*/, const QUrl & /*mxcUrl*/) {
|
|
||||||
#endif
|
|
||||||
if (id == txnId) {
|
if (id == txnId) {
|
||||||
setFileUploadingProgress(0);
|
setFileUploadingProgress(0);
|
||||||
setHasFileUploading(false);
|
setHasFileUploading(false);
|
||||||
@@ -205,7 +231,7 @@ void NeoChatRoom::sendTypingNotification(bool isTyping)
|
|||||||
connection()->callApi<SetTypingJob>(BackgroundRequest, localUser()->id(), id(), isTyping, 10000);
|
connection()->callApi<SetTypingJob>(BackgroundRequest, localUser()->id(), id(), isTyping, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const RoomEvent *NeoChatRoom::lastEvent(bool ignoreStateEvent) const
|
const RoomEvent *NeoChatRoom::lastEvent() const
|
||||||
{
|
{
|
||||||
for (auto timelineItem = messageEvents().rbegin(); timelineItem < messageEvents().rend(); timelineItem++) {
|
for (auto timelineItem = messageEvents().rbegin(); timelineItem < messageEvents().rend(); timelineItem++) {
|
||||||
const RoomEvent *event = timelineItem->get();
|
const RoomEvent *event = timelineItem->get();
|
||||||
@@ -217,8 +243,20 @@ const RoomEvent *NeoChatRoom::lastEvent(bool ignoreStateEvent) const
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event->isStateEvent()
|
if (event->isStateEvent() && !NeoChatConfig::showStateEvent()) {
|
||||||
&& (ignoreStateEvent || !NeoChatConfig::self()->showLeaveJoinEvent() || static_cast<const StateEventBase &>(*event).repeatsState())) {
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(event)) {
|
||||||
|
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::showLeaveJoinEvent()) {
|
||||||
|
continue;
|
||||||
|
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::showRename()) {
|
||||||
|
continue;
|
||||||
|
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::showAvatarUpdate()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event->isStateEvent() && static_cast<const StateEvent &>(*event).repeatsState()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,14 +270,20 @@ const RoomEvent *NeoChatRoom::lastEvent(bool ignoreStateEvent) const
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef QUOTIENT_07
|
||||||
|
if (auto lastEvent = eventCast<const StateEvent>(event)) {
|
||||||
|
#else
|
||||||
|
if (auto lastEvent = eventCast<const StateEventBase>(event)) {
|
||||||
|
#endif
|
||||||
|
return lastEvent;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto lastEvent = eventCast<const RoomMessageEvent>(event)) {
|
if (auto lastEvent = eventCast<const RoomMessageEvent>(event)) {
|
||||||
return lastEvent;
|
return lastEvent;
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
if (auto lastEvent = eventCast<const PollStartEvent>(event)) {
|
if (auto lastEvent = eventCast<const PollStartEvent>(event)) {
|
||||||
return lastEvent;
|
return lastEvent;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -257,12 +301,12 @@ bool NeoChatRoom::lastEventIsSpoiler() const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::lastEventToString() const
|
QString NeoChatRoom::lastEventToString(Qt::TextFormat format, bool stripNewlines) const
|
||||||
{
|
{
|
||||||
if (auto event = lastEvent()) {
|
if (auto event = lastEvent()) {
|
||||||
return roomMembername(event->senderId()) + (event->isStateEvent() ? " " : ": ") + eventToString(*event);
|
return safeMemberName(event->senderId()) + (event->isStateEvent() ? " " : ": ") + eventToString(*event, format, stripNewlines);
|
||||||
}
|
}
|
||||||
return QLatin1String("");
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::isEventHighlighted(const RoomEvent *e) const
|
bool NeoChatRoom::isEventHighlighted(const RoomEvent *e) const
|
||||||
@@ -278,7 +322,7 @@ void NeoChatRoom::checkForHighlights(const Quotient::TimelineItem &ti)
|
|||||||
}
|
}
|
||||||
if (auto *e = ti.viewAs<RoomMessageEvent>()) {
|
if (auto *e = ti.viewAs<RoomMessageEvent>()) {
|
||||||
const auto &text = e->plainBody();
|
const auto &text = e->plainBody();
|
||||||
if (text.contains(localUserId) || text.contains(roomMembername(localUserId))) {
|
if (text.contains(localUserId) || text.contains(safeMemberName(localUserId))) {
|
||||||
highlights.insert(e);
|
highlights.insert(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,7 +345,7 @@ void NeoChatRoom::onAddHistoricalTimelineEvents(rev_iter_t from)
|
|||||||
void NeoChatRoom::onRedaction(const RoomEvent &prevEvent, const RoomEvent & /*after*/)
|
void NeoChatRoom::onRedaction(const RoomEvent &prevEvent, const RoomEvent & /*after*/)
|
||||||
{
|
{
|
||||||
if (const auto &e = eventCast<const ReactionEvent>(&prevEvent)) {
|
if (const auto &e = eventCast<const ReactionEvent>(&prevEvent)) {
|
||||||
if (auto relatedEventId = e->relation().eventId; !relatedEventId.isEmpty()) {
|
if (auto relatedEventId = e->eventId(); !relatedEventId.isEmpty()) {
|
||||||
Q_EMIT updatedEvent(relatedEventId);
|
Q_EMIT updatedEvent(relatedEventId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,9 +353,8 @@ void NeoChatRoom::onRedaction(const RoomEvent &prevEvent, const RoomEvent & /*af
|
|||||||
|
|
||||||
void NeoChatRoom::countChanged()
|
void NeoChatRoom::countChanged()
|
||||||
{
|
{
|
||||||
if (displayed() && !hasUnreadMessages()) {
|
if (displayed() && unreadStats().empty()) {
|
||||||
resetNotificationCount();
|
setReadReceipt(lastEvent()->id());
|
||||||
resetHighlightCount();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +364,7 @@ QDateTime NeoChatRoom::lastActiveTime()
|
|||||||
return QDateTime();
|
return QDateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto event = lastEvent(true)) {
|
if (auto event = lastEvent()) {
|
||||||
return event->originTimestamp();
|
return event->originTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,69 +372,6 @@ QDateTime NeoChatRoom::lastActiveTime()
|
|||||||
return messageEvents().rbegin()->get()->originTimestamp();
|
return messageEvents().rbegin()->get()->originTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::subtitleText()
|
|
||||||
{
|
|
||||||
static const QRegularExpression blockquote("(\r\n\t|\n|\r\t|)> ");
|
|
||||||
static const QRegularExpression heading("(\r\n\t|\n|\r\t|)\\#{1,6} ");
|
|
||||||
static const QRegularExpression newlines("(\r\n\t|\n|\r\t|\r\n)");
|
|
||||||
static const QRegularExpression bold1("(\\*\\*|__)(?=\\S)([^\\r]*\\S)\\1");
|
|
||||||
static const QRegularExpression bold2("(\\*|_)(?=\\S)([^\\r]*\\S)\\1");
|
|
||||||
static const QRegularExpression strike1("~~(.*)~~");
|
|
||||||
static const QRegularExpression strike2("~(.*)~");
|
|
||||||
static const QRegularExpression del("<del>(.*)</del>");
|
|
||||||
static const QRegularExpression multileLineCode("```([^```]+)```");
|
|
||||||
static const QRegularExpression singleLinecode("`([^`]+)`");
|
|
||||||
QString subtitle = lastEventToString().size() == 0 ? topic() : lastEventToString();
|
|
||||||
|
|
||||||
subtitle
|
|
||||||
// replace blockquote, i.e. '> text'
|
|
||||||
.replace(blockquote, " ")
|
|
||||||
// replace headings, i.e. "# text"
|
|
||||||
.replace(heading, " ")
|
|
||||||
// replace newlines
|
|
||||||
.replace(newlines, " ")
|
|
||||||
// replace '**text**' and '__text__'
|
|
||||||
.replace(bold1, "\\2")
|
|
||||||
// replace '*text*' and '_text_'
|
|
||||||
.replace(bold2, "\\2")
|
|
||||||
// replace '~~text~~'
|
|
||||||
.replace(strike1, "\\1")
|
|
||||||
// replace '~text~'
|
|
||||||
.replace(strike2, "\\1")
|
|
||||||
// replace '<del>text</del>'
|
|
||||||
.replace(del, "\\1")
|
|
||||||
// replace '```code```'
|
|
||||||
.replace(multileLineCode, "\\1")
|
|
||||||
// replace '`code`'
|
|
||||||
.replace(singleLinecode, "\\1");
|
|
||||||
|
|
||||||
return subtitle.size() > 0 ? subtitle : QStringLiteral(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::savedTopVisibleIndex() const
|
|
||||||
{
|
|
||||||
return firstDisplayedMarker() == historyEdge() ? 0 : int(firstDisplayedMarker() - messageEvents().rbegin());
|
|
||||||
}
|
|
||||||
|
|
||||||
int NeoChatRoom::savedBottomVisibleIndex() const
|
|
||||||
{
|
|
||||||
return lastDisplayedMarker() == historyEdge() ? 0 : int(lastDisplayedMarker() - messageEvents().rbegin());
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::saveViewport(int topIndex, int bottomIndex)
|
|
||||||
{
|
|
||||||
if (topIndex == -1 || bottomIndex == -1 || (bottomIndex == savedBottomVisibleIndex() && (bottomIndex == 0 || topIndex == savedTopVisibleIndex()))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (bottomIndex == 0) {
|
|
||||||
setFirstDisplayedEventId({});
|
|
||||||
setLastDisplayedEventId({});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setFirstDisplayedEvent(maxTimelineIndex() - topIndex);
|
|
||||||
setLastDisplayedEvent(maxTimelineIndex() - bottomIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList NeoChatRoom::getUsers(const QString &keyword, int limit) const
|
QVariantList NeoChatRoom::getUsers(const QString &keyword, int limit) const
|
||||||
{
|
{
|
||||||
const auto userList = users();
|
const auto userList = users();
|
||||||
@@ -425,15 +405,6 @@ QVariantMap NeoChatRoom::getUser(const QString &userID) const
|
|||||||
{QStringLiteral("color"), user.color()}};
|
{QStringLiteral("color"), user.color()}};
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl NeoChatRoom::urlToMxcUrl(const QUrl &mxcUrl)
|
|
||||||
{
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return connection()->makeMediaUrl(mxcUrl);
|
|
||||||
#else
|
|
||||||
return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
QString NeoChatRoom::avatarMediaId() const
|
QString NeoChatRoom::avatarMediaId() const
|
||||||
{
|
{
|
||||||
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
|
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
|
||||||
@@ -451,87 +422,71 @@ QString NeoChatRoom::avatarMediaId() const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format, bool removeReply) const
|
QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format, bool stripNewlines) const
|
||||||
{
|
{
|
||||||
const bool prettyPrint = (format == Qt::RichText);
|
const bool prettyPrint = (format == Qt::RichText);
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return switchOnType(
|
return switchOnType(
|
||||||
#else
|
|
||||||
return visit(
|
|
||||||
#endif
|
|
||||||
evt,
|
evt,
|
||||||
[this, prettyPrint, removeReply](const RoomMessageEvent &e) {
|
[this, format, stripNewlines](const RoomMessageEvent &e) {
|
||||||
using namespace MessageEventContent;
|
using namespace MessageEventContent;
|
||||||
|
|
||||||
// 1. prettyPrint/HTML
|
TextHandler textHandler;
|
||||||
if (prettyPrint && e.hasTextContent() && e.mimeType().name() != "text/plain") {
|
|
||||||
auto htmlBody = static_cast<const TextContent *>(e.content())->body;
|
|
||||||
if (removeReply) {
|
|
||||||
htmlBody.remove(utils::removeRichReplyRegex);
|
|
||||||
}
|
|
||||||
htmlBody.replace(utils::userPillRegExp, R"(<b class="user-pill">\1</b>)");
|
|
||||||
htmlBody.replace(utils::strikethroughRegExp, "<s>\\1</s>");
|
|
||||||
|
|
||||||
auto url = connection()->homeserver();
|
|
||||||
auto base = url.scheme() + QStringLiteral("://") + url.host() + (url.port() != -1 ? ':' + QString::number(url.port()) : QString());
|
|
||||||
htmlBody.replace(utils::mxcImageRegExp, QStringLiteral(R"(<img \1 src="%1/_matrix/media/r0/download/\2/\3" \4 > )").arg(base));
|
|
||||||
|
|
||||||
return htmlBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.hasFileContent()) {
|
if (e.hasFileContent()) {
|
||||||
auto fileCaption = e.content()->fileInfo()->originalName.toHtmlEscaped();
|
auto fileCaption = e.content()->fileInfo()->originalName;
|
||||||
if (fileCaption.isEmpty()) {
|
if (fileCaption.isEmpty()) {
|
||||||
fileCaption = prettyPrint ? Quotient::prettyPrint(e.plainBody()) : e.plainBody();
|
fileCaption = e.plainBody();
|
||||||
} else if (e.content()->fileInfo()->originalName != e.plainBody()) {
|
} else if (e.content()->fileInfo()->originalName != e.plainBody()) {
|
||||||
fileCaption = e.plainBody() + " | " + fileCaption;
|
fileCaption = e.plainBody() + " | " + fileCaption;
|
||||||
}
|
}
|
||||||
return !fileCaption.isEmpty() ? fileCaption : i18n("a file");
|
textHandler.setData(fileCaption);
|
||||||
|
return !fileCaption.isEmpty() ? textHandler.handleRecievePlainText(Qt::PlainText, stripNewlines) : i18n("a file");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. prettyPrint/text 3. plainText/HTML 4. plainText/text
|
QString body;
|
||||||
QString plainBody;
|
if (e.hasTextContent() && e.content()) {
|
||||||
if (e.hasTextContent() && e.content() && e.mimeType().name() == "text/plain") { // 2/4
|
body = static_cast<const TextContent *>(e.content())->body;
|
||||||
plainBody = static_cast<const TextContent *>(e.content())->body;
|
} else {
|
||||||
} else { // 3
|
body = e.plainBody();
|
||||||
plainBody = e.plainBody();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prettyPrint) {
|
textHandler.setData(body);
|
||||||
if (removeReply) {
|
|
||||||
plainBody.remove(utils::removeReplyRegex);
|
Qt::TextFormat inputFormat;
|
||||||
}
|
if (e.mimeType().name() == "text/plain") {
|
||||||
return Quotient::prettyPrint(plainBody);
|
inputFormat = Qt::PlainText;
|
||||||
|
} else {
|
||||||
|
inputFormat = Qt::RichText;
|
||||||
}
|
}
|
||||||
if (removeReply) {
|
|
||||||
return plainBody.remove(utils::removeReplyRegex);
|
if (format == Qt::RichText) {
|
||||||
|
return textHandler.handleRecieveRichText(inputFormat, this, &e, stripNewlines);
|
||||||
|
} else {
|
||||||
|
return textHandler.handleRecievePlainText(inputFormat, stripNewlines);
|
||||||
}
|
}
|
||||||
return plainBody;
|
|
||||||
},
|
},
|
||||||
[](const StickerEvent &e) {
|
[](const StickerEvent &e) {
|
||||||
return e.body();
|
return e.body();
|
||||||
},
|
},
|
||||||
[this](const RoomMemberEvent &e) {
|
[this, prettyPrint](const RoomMemberEvent &e) {
|
||||||
// FIXME: Rewind to the name that was at the time of this event
|
// FIXME: Rewind to the name that was at the time of this event
|
||||||
auto subjectName = this->htmlSafeMemberName(e.userId());
|
auto subjectName = this->htmlSafeMemberName(e.userId());
|
||||||
if (e.membership() == MembershipType::Leave) {
|
if (e.membership() == Membership::Leave) {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
if (e.prevContent() && e.prevContent()->displayName) {
|
if (e.prevContent() && e.prevContent()->displayName) {
|
||||||
subjectName = sanitized(*e.prevContent()->displayName).toHtmlEscaped();
|
subjectName = sanitized(*e.prevContent()->displayName).toHtmlEscaped();
|
||||||
#else
|
|
||||||
if (e.prevContent() && e.prevContent()->displayName.isEmpty()) {
|
|
||||||
subjectName = sanitized(e.prevContent()->displayName).toHtmlEscaped();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>")
|
|
||||||
.arg(e.userId(), static_cast<NeoChatUser *>(user(e.userId()))->color().name(), subjectName);
|
if (prettyPrint) {
|
||||||
|
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>")
|
||||||
|
.arg(e.userId(), static_cast<NeoChatUser *>(user(e.userId()))->color().name(), subjectName);
|
||||||
|
}
|
||||||
|
|
||||||
// The below code assumes senderName output in AuthorRole
|
// The below code assumes senderName output in AuthorRole
|
||||||
switch (e.membership()) {
|
switch (e.membership()) {
|
||||||
case MembershipType::Invite:
|
case Membership::Invite:
|
||||||
if (e.repeatsState()) {
|
if (e.repeatsState()) {
|
||||||
auto text = i18n("reinvited %1 to the room", subjectName);
|
auto text = i18n("reinvited %1 to the room", subjectName);
|
||||||
if (!e.reason().isEmpty()) {
|
if (!e.reason().isEmpty()) {
|
||||||
@@ -540,13 +495,13 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
Q_FALLTHROUGH();
|
Q_FALLTHROUGH();
|
||||||
case MembershipType::Join: {
|
case Membership::Join: {
|
||||||
QString text{};
|
QString text{};
|
||||||
// Part 1: invites and joins
|
// Part 1: invites and joins
|
||||||
if (e.repeatsState()) {
|
if (e.repeatsState()) {
|
||||||
text = i18n("joined the room (repeated)");
|
text = i18n("joined the room (repeated)");
|
||||||
} else if (e.changesMembership()) {
|
} else if (e.changesMembership()) {
|
||||||
text = e.membership() == MembershipType::Invite ? i18n("invited %1 to the room", subjectName) : i18n("joined the room");
|
text = e.membership() == Membership::Invite ? i18n("invited %1 to the room", subjectName) : i18n("joined the room");
|
||||||
}
|
}
|
||||||
if (!text.isEmpty()) {
|
if (!text.isEmpty()) {
|
||||||
if (!e.reason().isEmpty()) {
|
if (!e.reason().isEmpty()) {
|
||||||
@@ -556,23 +511,19 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
}
|
}
|
||||||
// Part 2: profile changes of joined members
|
// Part 2: profile changes of joined members
|
||||||
if (e.isRename()) {
|
if (e.isRename()) {
|
||||||
if (e.displayName().isEmpty()) {
|
if (e.newDisplayName()) {
|
||||||
text = i18nc("their refers to a singular user", "cleared their display name");
|
text = i18nc("their refers to a singular user", "cleared their display name");
|
||||||
} else {
|
} else {
|
||||||
text = i18nc("their refers to a singular user", "changed their display name to %1", e.displayName().toHtmlEscaped());
|
text = i18nc("their refers to a singular user", "changed their display name to %1", e.newDisplayName()->toHtmlEscaped());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.isAvatarUpdate()) {
|
if (e.isAvatarUpdate()) {
|
||||||
if (!text.isEmpty()) {
|
if (!text.isEmpty()) {
|
||||||
text += i18n(" and ");
|
text += i18n(" and ");
|
||||||
}
|
}
|
||||||
if (e.avatarUrl().isEmpty()) {
|
if (e.newAvatarUrl()) {
|
||||||
text += i18nc("their refers to a singular user", "cleared their avatar");
|
text += i18nc("their refers to a singular user", "cleared their avatar");
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
} else if (!e.prevContent()->avatarUrl) {
|
} else if (!e.prevContent()->avatarUrl) {
|
||||||
#else
|
|
||||||
} else if (e.prevContent()->avatarUrl.isEmpty()) {
|
|
||||||
#endif
|
|
||||||
text += i18n("set an avatar");
|
text += i18n("set an avatar");
|
||||||
} else {
|
} else {
|
||||||
text += i18nc("their refers to a singular user", "updated their avatar");
|
text += i18nc("their refers to a singular user", "updated their avatar");
|
||||||
@@ -583,18 +534,18 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
case MembershipType::Leave:
|
case Membership::Leave:
|
||||||
if (e.prevContent() && e.prevContent()->membership == MembershipType::Invite) {
|
if (e.prevContent() && e.prevContent()->membership == Membership::Invite) {
|
||||||
return (e.senderId() != e.userId()) ? i18n("withdrew %1's invitation", subjectName) : i18n("rejected the invitation");
|
return (e.senderId() != e.userId()) ? i18n("withdrew %1's invitation", subjectName) : i18n("rejected the invitation");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.prevContent() && e.prevContent()->membership == MembershipType::Ban) {
|
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
|
||||||
return (e.senderId() != e.userId()) ? i18n("unbanned %1", subjectName) : i18n("self-unbanned");
|
return (e.senderId() != e.userId()) ? i18n("unbanned %1", subjectName) : i18n("self-unbanned");
|
||||||
}
|
}
|
||||||
return (e.senderId() != e.userId())
|
return (e.senderId() != e.userId())
|
||||||
? i18n("has put %1 out of the room: %2", subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped())
|
? i18n("has put %1 out of the room: %2", subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped())
|
||||||
: i18n("left the room");
|
: i18n("left the room");
|
||||||
case MembershipType::Ban:
|
case Membership::Ban:
|
||||||
if (e.senderId() != e.userId()) {
|
if (e.senderId() != e.userId()) {
|
||||||
if (e.reason().isEmpty()) {
|
if (e.reason().isEmpty()) {
|
||||||
return i18n("banned %1 from the room", subjectName);
|
return i18n("banned %1 from the room", subjectName);
|
||||||
@@ -604,7 +555,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
} else {
|
} else {
|
||||||
return i18n("self-banned from the room");
|
return i18n("self-banned from the room");
|
||||||
}
|
}
|
||||||
case MembershipType::Knock: {
|
case Membership::Knock: {
|
||||||
QString reason(e.contentJson()["reason"_ls].toString().toHtmlEscaped());
|
QString reason(e.contentJson()["reason"_ls].toString().toHtmlEscaped());
|
||||||
return reason.isEmpty() ? i18n("requested an invite") : i18n("requested an invite with reason: %1", reason);
|
return reason.isEmpty() ? i18n("requested an invite") : i18n("requested an invite with reason: %1", reason);
|
||||||
}
|
}
|
||||||
@@ -618,8 +569,12 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
[](const RoomNameEvent &e) {
|
[](const RoomNameEvent &e) {
|
||||||
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", e.name().toHtmlEscaped());
|
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", e.name().toHtmlEscaped());
|
||||||
},
|
},
|
||||||
[prettyPrint](const RoomTopicEvent &e) {
|
[prettyPrint, stripNewlines](const RoomTopicEvent &e) {
|
||||||
return (e.topic().isEmpty()) ? i18n("cleared the topic") : i18n("set the topic to: %1", prettyPrint ? Quotient::prettyPrint(e.topic()) : e.topic());
|
return (e.topic().isEmpty()) ? i18n("cleared the topic")
|
||||||
|
: i18n("set the topic to: %1",
|
||||||
|
prettyPrint ? Quotient::prettyPrint(e.topic())
|
||||||
|
: stripNewlines ? e.topic().replace(u'\n', u' ')
|
||||||
|
: e.topic());
|
||||||
},
|
},
|
||||||
[](const RoomAvatarEvent &) {
|
[](const RoomAvatarEvent &) {
|
||||||
return i18n("changed the room avatar");
|
return i18n("changed the room avatar");
|
||||||
@@ -634,7 +589,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
[](const RoomPowerLevelsEvent &) {
|
[](const RoomPowerLevelsEvent &) {
|
||||||
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
||||||
},
|
},
|
||||||
[](const StateEventBase &e) {
|
[](const StateEvent &e) {
|
||||||
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
||||||
return i18n("changed the server access control lists for this room");
|
return i18n("changed the server access control lists for this room");
|
||||||
}
|
}
|
||||||
@@ -650,21 +605,15 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
||||||
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
|
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
|
||||||
},
|
},
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
[](const PollStartEvent &e) {
|
[](const PollStartEvent &e) {
|
||||||
return e.question();
|
return e.question();
|
||||||
},
|
},
|
||||||
#endif
|
|
||||||
i18n("Unknown event"));
|
i18n("Unknown event"));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
|
QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return switchOnType(
|
return switchOnType(
|
||||||
#else
|
|
||||||
return visit(
|
|
||||||
#endif
|
|
||||||
evt,
|
evt,
|
||||||
[](const RoomMessageEvent &e) {
|
[](const RoomMessageEvent &e) {
|
||||||
Q_UNUSED(e)
|
Q_UNUSED(e)
|
||||||
@@ -676,25 +625,25 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
|
|||||||
},
|
},
|
||||||
[](const RoomMemberEvent &e) {
|
[](const RoomMemberEvent &e) {
|
||||||
switch (e.membership()) {
|
switch (e.membership()) {
|
||||||
case MembershipType::Invite:
|
case Membership::Invite:
|
||||||
if (e.repeatsState()) {
|
if (e.repeatsState()) {
|
||||||
return i18n("reinvited someone to the room");
|
return i18n("reinvited someone to the room");
|
||||||
}
|
}
|
||||||
Q_FALLTHROUGH();
|
Q_FALLTHROUGH();
|
||||||
case MembershipType::Join: {
|
case Membership::Join: {
|
||||||
QString text{};
|
QString text{};
|
||||||
// Part 1: invites and joins
|
// Part 1: invites and joins
|
||||||
if (e.repeatsState()) {
|
if (e.repeatsState()) {
|
||||||
text = i18n("joined the room (repeated)");
|
text = i18n("joined the room (repeated)");
|
||||||
} else if (e.changesMembership()) {
|
} else if (e.changesMembership()) {
|
||||||
text = e.membership() == MembershipType::Invite ? i18n("invited someone to the room") : i18n("joined the room");
|
text = e.membership() == Membership::Invite ? i18n("invited someone to the room") : i18n("joined the room");
|
||||||
}
|
}
|
||||||
if (!text.isEmpty()) {
|
if (!text.isEmpty()) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
// Part 2: profile changes of joined members
|
// Part 2: profile changes of joined members
|
||||||
if (e.isRename()) {
|
if (e.isRename()) {
|
||||||
if (e.displayName().isEmpty()) {
|
if (e.newDisplayName()) {
|
||||||
text = i18nc("their refers to a singular user", "cleared their display name");
|
text = i18nc("their refers to a singular user", "cleared their display name");
|
||||||
} else {
|
} else {
|
||||||
text = i18nc("their refers to a singular user", "changed their display name");
|
text = i18nc("their refers to a singular user", "changed their display name");
|
||||||
@@ -704,13 +653,9 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
|
|||||||
if (!text.isEmpty()) {
|
if (!text.isEmpty()) {
|
||||||
text += i18n(" and ");
|
text += i18n(" and ");
|
||||||
}
|
}
|
||||||
if (e.avatarUrl().isEmpty()) {
|
if (e.newAvatarUrl()) {
|
||||||
text += i18nc("their refers to a singular user", "cleared their avatar");
|
text += i18nc("their refers to a singular user", "cleared their avatar");
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
} else if (!e.prevContent()->avatarUrl) {
|
} else if (!e.prevContent()->avatarUrl) {
|
||||||
#else
|
|
||||||
} else if (e.prevContent()->avatarUrl.isEmpty()) {
|
|
||||||
#endif
|
|
||||||
text += i18n("set an avatar");
|
text += i18n("set an avatar");
|
||||||
} else {
|
} else {
|
||||||
text += i18nc("their refers to a singular user", "updated their avatar");
|
text += i18nc("their refers to a singular user", "updated their avatar");
|
||||||
@@ -721,22 +666,22 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
|
|||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
case MembershipType::Leave:
|
case Membership::Leave:
|
||||||
if (e.prevContent() && e.prevContent()->membership == MembershipType::Invite) {
|
if (e.prevContent() && e.prevContent()->membership == Membership::Invite) {
|
||||||
return (e.senderId() != e.userId()) ? i18n("withdrew a user's invitation") : i18n("rejected the invitation");
|
return (e.senderId() != e.userId()) ? i18n("withdrew a user's invitation") : i18n("rejected the invitation");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.prevContent() && e.prevContent()->membership == MembershipType::Ban) {
|
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
|
||||||
return (e.senderId() != e.userId()) ? i18n("unbanned a user") : i18n("self-unbanned");
|
return (e.senderId() != e.userId()) ? i18n("unbanned a user") : i18n("self-unbanned");
|
||||||
}
|
}
|
||||||
return (e.senderId() != e.userId()) ? i18n("put a user out of the room") : i18n("left the room");
|
return (e.senderId() != e.userId()) ? i18n("put a user out of the room") : i18n("left the room");
|
||||||
case MembershipType::Ban:
|
case Membership::Ban:
|
||||||
if (e.senderId() != e.userId()) {
|
if (e.senderId() != e.userId()) {
|
||||||
return i18n("banned a user from the room");
|
return i18n("banned a user from the room");
|
||||||
} else {
|
} else {
|
||||||
return i18n("self-banned from the room");
|
return i18n("self-banned from the room");
|
||||||
}
|
}
|
||||||
case MembershipType::Knock: {
|
case Membership::Knock: {
|
||||||
return i18n("requested an invite");
|
return i18n("requested an invite");
|
||||||
}
|
}
|
||||||
default:;
|
default:;
|
||||||
@@ -764,7 +709,7 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
|
|||||||
[](const RoomPowerLevelsEvent &) {
|
[](const RoomPowerLevelsEvent &) {
|
||||||
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
||||||
},
|
},
|
||||||
[](const StateEventBase &e) {
|
[](const StateEvent &e) {
|
||||||
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
||||||
return i18n("changed the server access control lists for this room");
|
return i18n("changed the server access control lists for this room");
|
||||||
}
|
}
|
||||||
@@ -779,56 +724,23 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
|
|||||||
}
|
}
|
||||||
return i18n("updated the state");
|
return i18n("updated the state");
|
||||||
},
|
},
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
[](const PollStartEvent &e) {
|
[](const PollStartEvent &e) {
|
||||||
|
Q_UNUSED(e);
|
||||||
return i18n("started a poll");
|
return i18n("started a poll");
|
||||||
},
|
},
|
||||||
#endif
|
|
||||||
i18n("Unknown event"));
|
i18n("Unknown event"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::changeAvatar(const QUrl &localFile)
|
void NeoChatRoom::changeAvatar(const QUrl &localFile)
|
||||||
{
|
{
|
||||||
const auto job = connection()->uploadFile(localFile.toLocalFile());
|
const auto job = connection()->uploadFile(localFile.toLocalFile());
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
if (isJobPending(job)) {
|
if (isJobPending(job)) {
|
||||||
#else
|
|
||||||
if (isJobRunning(job)) {
|
|
||||||
#endif
|
|
||||||
connect(job, &BaseJob::success, this, [this, job] {
|
connect(job, &BaseJob::success, this, [this, job] {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", QString(), QJsonObject{{"url", job->contentUri().toString()}});
|
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", QString(), QJsonObject{{"url", job->contentUri().toString()}});
|
||||||
#else
|
|
||||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", QString(), QJsonObject{{"url", job->contentUri()}});
|
|
||||||
#endif
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::addLocalAlias(const QString &alias)
|
|
||||||
{
|
|
||||||
auto a = aliases();
|
|
||||||
if (a.contains(alias)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
a += alias;
|
|
||||||
|
|
||||||
setLocalAliases(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NeoChatRoom::removeLocalAlias(const QString &alias)
|
|
||||||
{
|
|
||||||
auto a = aliases();
|
|
||||||
if (!a.contains(alias)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.removeAll(alias);
|
|
||||||
|
|
||||||
setLocalAliases(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString msgTypeToString(MessageEventType msgType)
|
QString msgTypeToString(MessageEventType msgType)
|
||||||
{
|
{
|
||||||
switch (msgType) {
|
switch (msgType) {
|
||||||
@@ -933,11 +845,11 @@ void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction
|
|||||||
|
|
||||||
QStringList redactEventIds; // What if there are multiple reaction events?
|
QStringList redactEventIds; // What if there are multiple reaction events?
|
||||||
|
|
||||||
const auto &annotations = relatedEvents(evt, EventRelation::Annotation());
|
const auto &annotations = relatedEvents(evt, EventRelation::AnnotationType);
|
||||||
if (!annotations.isEmpty()) {
|
if (!annotations.isEmpty()) {
|
||||||
for (const auto &a : annotations) {
|
for (const auto &a : annotations) {
|
||||||
if (auto e = eventCast<const ReactionEvent>(a)) {
|
if (auto e = eventCast<const ReactionEvent>(a)) {
|
||||||
if (e->relation().key != reaction) {
|
if (e->key() != reaction) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -960,18 +872,15 @@ void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction
|
|||||||
|
|
||||||
bool NeoChatRoom::containsUser(const QString &userID) const
|
bool NeoChatRoom::containsUser(const QString &userID) const
|
||||||
{
|
{
|
||||||
auto u = Room::user(userID);
|
return !isMember(userID);
|
||||||
|
|
||||||
if (!u) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Room::memberJoinState(u) != JoinState::Leave;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::canSendEvent(const QString &eventType) const
|
bool NeoChatRoom::canSendEvent(const QString &eventType) const
|
||||||
{
|
{
|
||||||
auto plEvent = getCurrentState<RoomPowerLevelsEvent>();
|
auto plEvent = currentState().get<RoomPowerLevelsEvent>();
|
||||||
|
if (!plEvent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
auto pl = plEvent->powerLevelForEvent(eventType);
|
auto pl = plEvent->powerLevelForEvent(eventType);
|
||||||
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
|
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
|
||||||
|
|
||||||
@@ -980,28 +889,19 @@ bool NeoChatRoom::canSendEvent(const QString &eventType) const
|
|||||||
|
|
||||||
bool NeoChatRoom::canSendState(const QString &eventType) const
|
bool NeoChatRoom::canSendState(const QString &eventType) const
|
||||||
{
|
{
|
||||||
auto plEvent = getCurrentState<RoomPowerLevelsEvent>();
|
auto plEvent = currentState().get<RoomPowerLevelsEvent>();
|
||||||
|
if (!plEvent) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
auto pl = plEvent->powerLevelForState(eventType);
|
auto pl = plEvent->powerLevelForState(eventType);
|
||||||
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
|
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
|
||||||
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
if (eventType == "m.room.history_visibility") {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return currentPl >= pl;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return currentPl >= pl;
|
return currentPl >= pl;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::readMarkerLoaded() const
|
bool NeoChatRoom::readMarkerLoaded() const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
const auto it = findInTimeline(lastFullyReadEventId());
|
const auto it = findInTimeline(lastFullyReadEventId());
|
||||||
#else
|
|
||||||
const auto it = findInTimeline(readMarkerEventId());
|
|
||||||
#endif
|
|
||||||
return it != historyEdge();
|
return it != historyEdge();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,12 +912,7 @@ bool NeoChatRoom::isInvite() const
|
|||||||
|
|
||||||
bool NeoChatRoom::isUserBanned(const QString &user) const
|
bool NeoChatRoom::isUserBanned(const QString &user) const
|
||||||
{
|
{
|
||||||
return getCurrentState<RoomMemberEvent>(user)->membership() == MembershipType::Ban;
|
return currentState().get<RoomMemberEvent>(user)->membership() == Membership::Ban;
|
||||||
}
|
|
||||||
|
|
||||||
QString NeoChatRoom::htmlSafeName() const
|
|
||||||
{
|
|
||||||
return name().toHtmlEscaped();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::htmlSafeDisplayName() const
|
QString NeoChatRoom::htmlSafeDisplayName() const
|
||||||
@@ -1032,7 +927,7 @@ void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reaso
|
|||||||
|
|
||||||
QString NeoChatRoom::joinRule() const
|
QString NeoChatRoom::joinRule() const
|
||||||
{
|
{
|
||||||
return getCurrentState<JoinRulesEvent>()->joinRule();
|
return currentState().get<JoinRulesEvent>()->joinRule();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::setJoinRule(const QString &joinRule)
|
void NeoChatRoom::setJoinRule(const QString &joinRule)
|
||||||
@@ -1041,21 +936,13 @@ void NeoChatRoom::setJoinRule(const QString &joinRule)
|
|||||||
qWarning() << "Power level too low to set join rules";
|
qWarning() << "Power level too low to set join rules";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
setState("m.room.join_rules", "", QJsonObject{{"join_rule", joinRule}});
|
setState("m.room.join_rules", "", QJsonObject{{"join_rule", joinRule}});
|
||||||
#else
|
|
||||||
setState<JoinRulesEvent>(QJsonObject{{"type", "m.room.join_rules"}, {"state_key", ""}, {"content", QJsonObject{{"join_rule", joinRule}}}});
|
|
||||||
#endif
|
|
||||||
// Not emitting joinRuleChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
// Not emitting joinRuleChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
||||||
}
|
}
|
||||||
|
|
||||||
QString NeoChatRoom::historyVisibility() const
|
QString NeoChatRoom::historyVisibility() const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return currentState().get("m.room.history_visibility")->contentJson()["history_visibility"_ls].toString();
|
return currentState().get("m.room.history_visibility")->contentJson()["history_visibility"_ls].toString();
|
||||||
#else
|
|
||||||
return getCurrentState("m.room.history_visibility")->contentJson()["history_visibility"_ls].toString();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
|
void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
|
||||||
@@ -1065,20 +952,85 @@ void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
setState("m.room.history_visibility", "", QJsonObject{{"history_visibility", historyVisibilityRule}});
|
setState("m.room.history_visibility", "", QJsonObject{{"history_visibility", historyVisibilityRule}});
|
||||||
#else
|
|
||||||
qWarning() << "Quotient 0.7 required to set history visibility";
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Not emitting historyVisibilityChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
// Not emitting historyVisibilityChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
||||||
}
|
}
|
||||||
|
|
||||||
int NeoChatRoom::getUserPowerLevel(const QString &userId) const
|
bool NeoChatRoom::defaultUrlPreviewState() const
|
||||||
{
|
{
|
||||||
auto powerLevelEvent = getCurrentState<RoomPowerLevelsEvent>();
|
auto urlPreviewsDisabled = currentState().get("org.matrix.room.preview_urls");
|
||||||
return powerLevelEvent->powerLevelForUser(userId);
|
|
||||||
|
// Some rooms will not have this state event set so check for a nullptr return.
|
||||||
|
if (urlPreviewsDisabled != nullptr) {
|
||||||
|
return !urlPreviewsDisabled->contentJson()["disable"].toBool();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setDefaultUrlPreviewState(const bool &defaultUrlPreviewState)
|
||||||
|
{
|
||||||
|
if (!canSendState("org.matrix.room.preview_urls")) {
|
||||||
|
qWarning() << "Power level too low to set the default URL preview state for the room";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note the org.matrix.room.preview_urls room state event is completely undocumented
|
||||||
|
* so here it is because I'm nice.
|
||||||
|
*
|
||||||
|
* Also note this is a different event to org.matrix.room.preview_urls for room
|
||||||
|
* account data, because even though it has the same name and content it's totally different.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "content": {
|
||||||
|
* "disable": false
|
||||||
|
* },
|
||||||
|
* "origin_server_ts": 1673115224071,
|
||||||
|
* "sender": "@bob:kde.org",
|
||||||
|
* "state_key": "",
|
||||||
|
* "type": "org.matrix.room.preview_urls",
|
||||||
|
* "unsigned": {
|
||||||
|
* "replaces_state": "replaced_event_id",
|
||||||
|
* "prev_content": {
|
||||||
|
* "disable": true
|
||||||
|
* },
|
||||||
|
* "prev_sender": "@jeff:kde.org",
|
||||||
|
* "age": 99
|
||||||
|
* },
|
||||||
|
* "event_id": "$event_id",
|
||||||
|
* "room_id": "!room_id:kde.org"
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* You just have to set disable to true to disable URL previews by default.
|
||||||
|
*/
|
||||||
|
setState("org.matrix.room.preview_urls", "", QJsonObject{{"disable", !defaultUrlPreviewState}});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeoChatRoom::urlPreviewEnabled() const
|
||||||
|
{
|
||||||
|
if (hasAccountData("org.matrix.room.preview_urls")) {
|
||||||
|
return !accountData("org.matrix.room.preview_urls")->contentJson()["disable"].toBool();
|
||||||
|
} else {
|
||||||
|
return defaultUrlPreviewState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setUrlPreviewEnabled(const bool &urlPreviewEnabled)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Once again this is undocumented and even though the name and content are the
|
||||||
|
* same this is a different event to the org.matrix.room.preview_urls room state event.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "content": {
|
||||||
|
* "disable": true
|
||||||
|
* }
|
||||||
|
* "type": "org.matrix.room.preview_urls",
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
connection()->callApi<SetAccountDataPerRoomJob>(localUser()->id(), id(), "org.matrix.room.preview_urls", QJsonObject{{"disable", !urlPreviewEnabled}});
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel)
|
void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel)
|
||||||
@@ -1091,42 +1043,32 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
|
|||||||
qWarning() << "Power level too low to set user power levels";
|
qWarning() << "Power level too low to set user power levels";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
if (!isMember(userID)) {
|
if (!isMember(userID)) {
|
||||||
#else
|
|
||||||
if (memberJoinState(user(userID)) == JoinState::Join) {
|
|
||||||
#endif
|
|
||||||
qWarning() << "User is not a member of this room so power level cannot be set";
|
qWarning() << "User is not a member of this room so power level cannot be set";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int clampPowerLevel = std::clamp(powerLevel, 0, 100);
|
int clampPowerLevel = std::clamp(powerLevel, 0, 100);
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
|
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
|
||||||
#else
|
|
||||||
auto powerLevelContent = getCurrentState<RoomPowerLevelsEvent>()->contentJson();
|
|
||||||
#endif
|
|
||||||
auto powerLevelUserOverrides = powerLevelContent["users"].toObject();
|
auto powerLevelUserOverrides = powerLevelContent["users"].toObject();
|
||||||
|
|
||||||
if (powerLevelUserOverrides[userID] != clampPowerLevel) {
|
if (powerLevelUserOverrides[userID] != clampPowerLevel) {
|
||||||
powerLevelUserOverrides[userID] = clampPowerLevel;
|
powerLevelUserOverrides[userID] = clampPowerLevel;
|
||||||
powerLevelContent["users"] = powerLevelUserOverrides;
|
powerLevelContent["users"] = powerLevelUserOverrides;
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
setState("m.room.power_levels", "", powerLevelContent);
|
setState("m.room.power_levels", "", powerLevelContent);
|
||||||
#else
|
|
||||||
setState<RoomPowerLevelsEvent>(QJsonObject{{"type", "m.room.power_levels"}, {"state_key", ""}, {"content", powerLevelContent}});
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::getUserPowerLevel(const QString &userId) const
|
||||||
|
{
|
||||||
|
auto powerLevelEvent = getCurrentState<RoomPowerLevelsEvent>();
|
||||||
|
return powerLevelEvent->powerLevelForUser(userId);
|
||||||
|
}
|
||||||
|
|
||||||
int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent) const
|
int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent) const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
const auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
|
const auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
|
||||||
#else
|
|
||||||
const auto powerLevelEvent = getCurrentState<RoomPowerLevelsEvent>();
|
|
||||||
#endif
|
|
||||||
if (eventName == "ban") {
|
if (eventName == "ban") {
|
||||||
return powerLevelEvent->ban();
|
return powerLevelEvent->ban();
|
||||||
} else if (eventName == "kick") {
|
} else if (eventName == "kick") {
|
||||||
@@ -1150,11 +1092,7 @@ int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent)
|
|||||||
|
|
||||||
void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent)
|
void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent)
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
|
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
|
||||||
#else
|
|
||||||
auto powerLevelContent = getCurrentState<RoomPowerLevelsEvent>()->contentJson();
|
|
||||||
#endif
|
|
||||||
int clampPowerLevel = std::clamp(newPowerLevel, 0, 100);
|
int clampPowerLevel = std::clamp(newPowerLevel, 0, 100);
|
||||||
int powerLevel = 0;
|
int powerLevel = 0;
|
||||||
|
|
||||||
@@ -1183,11 +1121,7 @@ void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLev
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
setState("m.room.power_levels", "", powerLevelContent);
|
setState("m.room.power_levels", "", powerLevelContent);
|
||||||
#else
|
|
||||||
setState<RoomPowerLevelsEvent>(QJsonObject{{"type", "m.room.power_levels"}, {"state_key", ""}, {"content", powerLevelContent}});
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int NeoChatRoom::defaultUserPowerLevel() const
|
int NeoChatRoom::defaultUserPowerLevel() const
|
||||||
@@ -1410,11 +1344,12 @@ bool NeoChatRoom::isSpace()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
return creationEvent->roomType() == RoomType::Space;
|
return creationEvent->roomType() == RoomType::Space;
|
||||||
#else
|
}
|
||||||
return false;
|
|
||||||
#endif
|
PushNotificationState::State NeoChatRoom::pushNotificationState() const
|
||||||
|
{
|
||||||
|
return m_currentPushNotificationState;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||||
@@ -1736,15 +1671,9 @@ void NeoChatRoom::setSavedText(const QString &savedText)
|
|||||||
|
|
||||||
bool NeoChatRoom::canEncryptRoom() const
|
bool NeoChatRoom::canEncryptRoom() const
|
||||||
{
|
{
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#ifdef Quotient_E2EE_ENABLED
|
|
||||||
return !usesEncryption() && canSendState("m.room.encryption");
|
return !usesEncryption() && canSendState("m.room.encryption");
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
PollHandler *NeoChatRoom::poll(const QString &eventId)
|
PollHandler *NeoChatRoom::poll(const QString &eventId)
|
||||||
{
|
{
|
||||||
if (!m_polls.contains(eventId)) {
|
if (!m_polls.contains(eventId)) {
|
||||||
@@ -1755,7 +1684,6 @@ PollHandler *NeoChatRoom::poll(const QString &eventId)
|
|||||||
}
|
}
|
||||||
return m_polls[eventId];
|
return m_polls[eventId];
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
bool NeoChatRoom::downloadTempFile(const QString &eventId)
|
bool NeoChatRoom::downloadTempFile(const QString &eventId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qobjectdefs.h>
|
|
||||||
#include <room.h>
|
#include <room.h>
|
||||||
|
|
||||||
#include <QCache>
|
#include <QCache>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
|
||||||
#include <qcoro/task.h>
|
#include <QCoroTask>
|
||||||
|
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
@@ -20,78 +19,128 @@ class PushNotificationState : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Describes the push notification state for the room.
|
||||||
|
*/
|
||||||
enum State {
|
enum State {
|
||||||
Unknown,
|
Unknown, /**< The state has not yet been obtained from the server. */
|
||||||
Default,
|
Default, /**< The room follows the globally configured rules for the local user. */
|
||||||
Mute,
|
Mute, /**< No notifications for messages in the room. */
|
||||||
MentionKeyword,
|
MentionKeyword, /**< Notifications only for local user mentions and keywords. */
|
||||||
All,
|
All, /**< Notifications for all messages. */
|
||||||
};
|
};
|
||||||
Q_ENUM(State);
|
Q_ENUM(State);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines a user mention in the current chat or edit text.
|
||||||
|
*/
|
||||||
struct Mention {
|
struct Mention {
|
||||||
QTextCursor cursor;
|
QTextCursor cursor; /**< Contains the mention's text and position in the text. */
|
||||||
QString text;
|
QString text; /**< The inserted text of the mention. */
|
||||||
int start = 0;
|
int start = 0; /**< Start position of the mention. */
|
||||||
int position = 0;
|
int position = 0; /**< End position of the mention. */
|
||||||
QString id;
|
QString id; /**< The id the mention (used to create link when sending the message). */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class NeoChatRoom
|
||||||
|
*
|
||||||
|
* This class is designed to act as a wrapper over Quotient::Room to provide API and
|
||||||
|
* functionality not available in Quotient::Room.
|
||||||
|
*
|
||||||
|
* The functions fall into two main categories:
|
||||||
|
* - Helper functions to make functionality easily accessible in QML.
|
||||||
|
* - Implement functions not yet available in Quotient::Room.
|
||||||
|
*
|
||||||
|
* @sa Quotient::Room
|
||||||
|
*/
|
||||||
class NeoChatRoom : public Quotient::Room
|
class NeoChatRoom : public Quotient::Room
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
|
|
||||||
Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE setHasFileUploading NOTIFY hasFileUploadingChanged)
|
|
||||||
Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY fileUploadingProgressChanged)
|
|
||||||
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
|
|
||||||
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
|
||||||
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
|
|
||||||
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
|
||||||
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
|
|
||||||
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
|
|
||||||
Q_PROPERTY(QString historyVisibility READ historyVisibility WRITE setHistoryVisibility NOTIFY historyVisibilityChanged)
|
|
||||||
|
|
||||||
// Properties for the various permission levels for the room
|
|
||||||
Q_PROPERTY(int defaultUserPowerLevel READ defaultUserPowerLevel WRITE setDefaultUserPowerLevel NOTIFY defaultUserPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int invitePowerLevel READ invitePowerLevel WRITE setInvitePowerLevel NOTIFY invitePowerLevelChanged)
|
|
||||||
Q_PROPERTY(int kickPowerLevel READ kickPowerLevel WRITE setKickPowerLevel NOTIFY kickPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int banPowerLevel READ banPowerLevel WRITE setBanPowerLevel NOTIFY banPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int redactPowerLevel READ redactPowerLevel WRITE setRedactPowerLevel NOTIFY redactPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int statePowerLevel READ statePowerLevel WRITE setStatePowerLevel NOTIFY statePowerLevelChanged)
|
|
||||||
Q_PROPERTY(int defaultEventPowerLevel READ defaultEventPowerLevel WRITE setDefaultEventPowerLevel NOTIFY defaultEventPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int powerLevelPowerLevel READ powerLevelPowerLevel WRITE setPowerLevelPowerLevel NOTIFY powerLevelPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int namePowerLevel READ namePowerLevel WRITE setNamePowerLevel NOTIFY namePowerLevelChanged)
|
|
||||||
Q_PROPERTY(int avatarPowerLevel READ avatarPowerLevel WRITE setAvatarPowerLevel NOTIFY avatarPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int canonicalAliasPowerLevel READ canonicalAliasPowerLevel WRITE setCanonicalAliasPowerLevel NOTIFY canonicalAliasPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int topicPowerLevel READ topicPowerLevel WRITE setTopicPowerLevel NOTIFY topicPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int encryptionPowerLevel READ encryptionPowerLevel WRITE setEncryptionPowerLevel NOTIFY encryptionPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int historyVisibilityPowerLevel READ historyVisibilityPowerLevel WRITE setHistoryVisibilityPowerLevel NOTIFY historyVisibilityPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int pinnedEventsPowerLevel READ pinnedEventsPowerLevel WRITE setPinnedEventsPowerLevel NOTIFY pinnedEventsPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int tombstonePowerLevel READ tombstonePowerLevel WRITE setTombstonePowerLevel NOTIFY tombstonePowerLevelChanged)
|
|
||||||
Q_PROPERTY(int serverAclPowerLevel READ serverAclPowerLevel WRITE setServerAclPowerLevel NOTIFY serverAclPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int spaceChildPowerLevel READ spaceChildPowerLevel WRITE setSpaceChildPowerLevel NOTIFY spaceChildPowerLevelChanged)
|
|
||||||
Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged)
|
|
||||||
|
|
||||||
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
|
||||||
Q_PROPERTY(PushNotificationState::State pushNotificationState MEMBER m_currentPushNotificationState WRITE setPushNotificationState NOTIFY
|
|
||||||
pushNotificationStateChanged)
|
|
||||||
|
|
||||||
// Due to problems with QTextDocument, unlike the other properties here, chatBoxText is *not* used to store the text when switching rooms
|
|
||||||
Q_PROPERTY(QString chatBoxText READ chatBoxText WRITE setChatBoxText NOTIFY chatBoxTextChanged)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The text for any message currently being edited in the room.
|
* @brief A list of users currently typing in the room.
|
||||||
|
*
|
||||||
|
* The list does not include the local user.
|
||||||
|
*
|
||||||
|
* This is different to getting a list of NeoChatUser objects or Quotient::User objects
|
||||||
|
* as neither of those can provide details like the displayName or avatarMediaId
|
||||||
|
* without the room context as these can vary from room to room. This function
|
||||||
|
* provides the room context and puts the result as a list of QVariantMap objects.
|
||||||
|
*
|
||||||
|
* @return a QVariantMap for the user with the following
|
||||||
|
* parameters:
|
||||||
|
* - id - User ID.
|
||||||
|
* - avatarMediaId - Avatar id in the context of this room.
|
||||||
|
* - displayName - Display name in the context of this room.
|
||||||
|
* - display - Name in the context of this room.
|
||||||
|
*
|
||||||
|
* @sa Quotient::User, NeoChatUser
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(QString editText READ editText WRITE setEditText NOTIFY editTextChanged)
|
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
|
||||||
Q_PROPERTY(QString chatBoxReplyId READ chatBoxReplyId WRITE setChatBoxReplyId NOTIFY chatBoxReplyIdChanged)
|
|
||||||
Q_PROPERTY(QString chatBoxEditId READ chatBoxEditId WRITE setChatBoxEditId NOTIFY chatBoxEditIdChanged)
|
/**
|
||||||
Q_PROPERTY(NeoChatUser *chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
|
* @brief Convenience function to get the QDateTime of the last event.
|
||||||
Q_PROPERTY(QString chatBoxReplyMessage READ chatBoxReplyMessage NOTIFY chatBoxReplyIdChanged)
|
*
|
||||||
Q_PROPERTY(NeoChatUser *chatBoxEditUser READ chatBoxEditUser NOTIFY chatBoxEditIdChanged)
|
* @sa lastEvent()
|
||||||
Q_PROPERTY(QString chatBoxEditMessage READ chatBoxEditMessage NOTIFY chatBoxEditIdChanged)
|
*/
|
||||||
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
|
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
|
||||||
Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged)
|
|
||||||
|
/**
|
||||||
|
* @brief Whether a file is being uploaded to the server.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool hasFileUploading READ hasFileUploading WRITE setHasFileUploading NOTIFY hasFileUploadingChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Progress of a file upload as a percentage 0 - 100%.
|
||||||
|
*
|
||||||
|
* The value will be 0 if no file is uploading.
|
||||||
|
*
|
||||||
|
* @sa hasFileUploading
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int fileUploadingProgress READ fileUploadingProgress NOTIFY fileUploadingProgressChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the read marker should be shown.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Display name with any html special characters escaped.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The avatar image to be used for the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a user object for the other person in a direct chat.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeoChatUser *directChatRemoteUser READ directChatRemoteUser CONSTANT)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief If the room is a space.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the local user has an invite to the room.
|
||||||
|
*
|
||||||
|
* False for any other state including if the local user is a member.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current join rule for the room as a QString.
|
||||||
|
*
|
||||||
|
* Possible values are [public, knock, invite, private, restricted].
|
||||||
|
*
|
||||||
|
* @sa https://spec.matrix.org/v1.5/client-server-api/#mroomjoin_rules
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the maximum room version that the server supports.
|
* @brief Get the maximum room version that the server supports.
|
||||||
@@ -99,60 +148,482 @@ class NeoChatRoom : public Quotient::Room
|
|||||||
* Only returns main integer room versions (i.e. no msc room versions).
|
* Only returns main integer room versions (i.e. no msc room versions).
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(int maxRoomVersion READ maxRoomVersion NOTIFY maxRoomVersionChanged)
|
Q_PROPERTY(int maxRoomVersion READ maxRoomVersion NOTIFY maxRoomVersionChanged)
|
||||||
Q_PROPERTY(NeoChatUser *directChatRemoteUser READ directChatRemoteUser CONSTANT)
|
|
||||||
|
/**
|
||||||
|
* @brief The rule for which messages should generate notifications for the room.
|
||||||
|
*
|
||||||
|
* @sa PushNotificationState::State
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(PushNotificationState::State pushNotificationState READ pushNotificationState WRITE setPushNotificationState NOTIFY pushNotificationStateChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current history visibilty setting for the room.
|
||||||
|
*
|
||||||
|
* Possible values are [invited, joined, shared, world_readable].
|
||||||
|
*
|
||||||
|
* @sa https://spec.matrix.org/v1.5/client-server-api/#room-history-visibility
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString historyVisibility READ historyVisibility WRITE setHistoryVisibility NOTIFY historyVisibilityChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the default URL preview state for room members.
|
||||||
|
*
|
||||||
|
* Assumed false if the org.matrix.room.preview_urls state message has never been
|
||||||
|
* set. Can only be set if the calling user has a high enough power level.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool defaultUrlPreviewState READ defaultUrlPreviewState WRITE setDefaultUrlPreviewState NOTIFY defaultUrlPreviewStateChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enable URL previews for the local user.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool urlPreviewEnabled READ urlPreviewEnabled WRITE setUrlPreviewEnabled NOTIFY urlPreviewEnabledChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the local user can encrypt the room.
|
||||||
|
*
|
||||||
|
* Requires libQuotient 0.7 compiled with the Quotient_E2EE_ENABLED parameter to
|
||||||
|
* be able to return true.
|
||||||
|
*
|
||||||
|
* A local user can encrypt a room if they have permission to send the m.room.encryption
|
||||||
|
* state event.
|
||||||
|
*
|
||||||
|
* @sa https://spec.matrix.org/v1.5/client-server-api/#mroomencryption
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The default power level in the room for new users.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int defaultUserPowerLevel READ defaultUserPowerLevel WRITE setDefaultUserPowerLevel NOTIFY defaultUserPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to invite users to the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int invitePowerLevel READ invitePowerLevel WRITE setInvitePowerLevel NOTIFY invitePowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to kick users from the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int kickPowerLevel READ kickPowerLevel WRITE setKickPowerLevel NOTIFY kickPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to ban users from the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int banPowerLevel READ banPowerLevel WRITE setBanPowerLevel NOTIFY banPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to delete other user messages.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int redactPowerLevel READ redactPowerLevel WRITE setRedactPowerLevel NOTIFY redactPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The default power level for state events that are not explicitly specified.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int statePowerLevel READ statePowerLevel WRITE setStatePowerLevel NOTIFY statePowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The default power level for event that are not explicitly specified.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int defaultEventPowerLevel READ defaultEventPowerLevel WRITE setDefaultEventPowerLevel NOTIFY defaultEventPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change power levels for the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int powerLevelPowerLevel READ powerLevelPowerLevel WRITE setPowerLevelPowerLevel NOTIFY powerLevelPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room name.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int namePowerLevel READ namePowerLevel WRITE setNamePowerLevel NOTIFY namePowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room avatar.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int avatarPowerLevel READ avatarPowerLevel WRITE setAvatarPowerLevel NOTIFY avatarPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room aliases.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int canonicalAliasPowerLevel READ canonicalAliasPowerLevel WRITE setCanonicalAliasPowerLevel NOTIFY canonicalAliasPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room topic.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int topicPowerLevel READ topicPowerLevel WRITE setTopicPowerLevel NOTIFY topicPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to encrypt the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int encryptionPowerLevel READ encryptionPowerLevel WRITE setEncryptionPowerLevel NOTIFY encryptionPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to change the room history visibility.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int historyVisibilityPowerLevel READ historyVisibilityPowerLevel WRITE setHistoryVisibilityPowerLevel NOTIFY historyVisibilityPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to pin events in the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int pinnedEventsPowerLevel READ pinnedEventsPowerLevel WRITE setPinnedEventsPowerLevel NOTIFY pinnedEventsPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to upgrade the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int tombstonePowerLevel READ tombstonePowerLevel WRITE setTombstonePowerLevel NOTIFY tombstonePowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to set the room server access control list (ACL).
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int serverAclPowerLevel READ serverAclPowerLevel WRITE setServerAclPowerLevel NOTIFY serverAclPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to add children to a space.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int spaceChildPowerLevel READ spaceChildPowerLevel WRITE setSpaceChildPowerLevel NOTIFY spaceChildPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The power level required to set the room parent space.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current text in the chatbox for the room.
|
||||||
|
*
|
||||||
|
* Due to problems with QTextDocument, unlike the other properties here,
|
||||||
|
* chatBoxText is *not* used to store the text when switching rooms.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxText READ chatBoxText WRITE setChatBoxText NOTIFY chatBoxTextChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The text for any message currently being edited in the room.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString editText READ editText WRITE setEditText NOTIFY editTextChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The event id of a message being replied to.
|
||||||
|
*
|
||||||
|
* Will be QString() if not replying to a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxReplyId READ chatBoxReplyId WRITE setChatBoxReplyId NOTIFY chatBoxReplyIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The event id of a message being edited.
|
||||||
|
*
|
||||||
|
* Will be QString() if not editing to a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxEditId READ chatBoxEditId WRITE setChatBoxEditId NOTIFY chatBoxEditIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the user object for the message being replied to.
|
||||||
|
*
|
||||||
|
* Returns a nullptr if not replying to a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeoChatUser *chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The content of the message being replied to.
|
||||||
|
*
|
||||||
|
* Will be QString() if not replying to a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxReplyMessage READ chatBoxReplyMessage NOTIFY chatBoxReplyIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the user object for the message being edited.
|
||||||
|
*
|
||||||
|
* Returns a nullptr if not editing a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeoChatUser *chatBoxEditUser READ chatBoxEditUser NOTIFY chatBoxEditIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The content of the message being edited.
|
||||||
|
*
|
||||||
|
* Will be QString() if not editing a message.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxEditMessage READ chatBoxEditMessage NOTIFY chatBoxEditIdChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The file path of the attachment to be sent.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Define the types on inline messages that can be shown.
|
||||||
|
*/
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
Positive,
|
Positive, /**< Positive message, typically green. */
|
||||||
Info,
|
Info, /**< Info message, typically highlight color. */
|
||||||
Error,
|
Error, /**< Error message, typically red. */
|
||||||
};
|
};
|
||||||
Q_ENUM(MessageType);
|
Q_ENUM(MessageType);
|
||||||
|
|
||||||
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
|
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a list of users in the context of this room.
|
||||||
|
*
|
||||||
|
* This is different to getting a list of NeoChatUser objects or Quotient::User objects
|
||||||
|
* as neither of those can provide details like the displayName or avatarMediaId
|
||||||
|
* without the room context as these can vary from room to room. This function
|
||||||
|
* provides the room context and returns the result as a list of QVariantMap objects.
|
||||||
|
*
|
||||||
|
* @param keyword filters the users based on the displayname containing keyword.
|
||||||
|
* @param limit max number of user returned, -1 is infinite.
|
||||||
|
*
|
||||||
|
* @return a QVariantList containing a QVariantMap for each user with the following
|
||||||
|
* properties:
|
||||||
|
* - id - User ID.
|
||||||
|
* - displayName - Display name in the context of this room.
|
||||||
|
* - avatarMediaId - Avatar id in the context of this room.
|
||||||
|
* - color - Color for the user.
|
||||||
|
*
|
||||||
|
* @sa Quotient::User, NeoChatUser
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] QVariantList getUsers(const QString &keyword, int limit = -1) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a user in the context of this room.
|
||||||
|
*
|
||||||
|
* This is different to getting a NeoChatUser object or Quotient::User object
|
||||||
|
* as neither of those can provide details like the displayName or avatarMediaId
|
||||||
|
* without the room context as these can vary from room to room. This function
|
||||||
|
* provides the room context and outputs the result as QVariantMap.
|
||||||
|
*
|
||||||
|
* @param userID the ID of the user to output.
|
||||||
|
*
|
||||||
|
* @return a QVariantMap for the user with the following properties:
|
||||||
|
* - id - User ID.
|
||||||
|
* - displayName - Display name in the context of this room.
|
||||||
|
* - avatarMediaId - Avatar id in the context of this room.
|
||||||
|
* - color - Color for the user.
|
||||||
|
*
|
||||||
|
* @sa Quotient::User, NeoChatUser
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const;
|
||||||
|
|
||||||
[[nodiscard]] QVariantList getUsersTyping() const;
|
[[nodiscard]] QVariantList getUsersTyping() const;
|
||||||
|
|
||||||
/// Get the interesting last event.
|
|
||||||
///
|
|
||||||
/// This function respect the showLeaveJoinEvent setting and discard
|
|
||||||
/// other not interesting events. This function can return an empty pointer
|
|
||||||
/// when the room is empty of RoomMessageEvent.
|
|
||||||
[[nodiscard]] const Quotient::RoomEvent *lastEvent(bool ignoreStateEvent = false) const;
|
|
||||||
|
|
||||||
/// Convenient way to get the last event but in a string format.
|
|
||||||
///
|
|
||||||
/// \see lastEvent
|
|
||||||
/// \see lastEventIsSpoiler
|
|
||||||
[[nodiscard]] QString lastEventToString() const;
|
|
||||||
|
|
||||||
/// Convenient way to check if the last event looks like it has spoilers.
|
|
||||||
///
|
|
||||||
/// \see lastEvent
|
|
||||||
/// \see lastEventToString
|
|
||||||
[[nodiscard]] bool lastEventIsSpoiler() const;
|
|
||||||
|
|
||||||
/// Convenient way to get the QDateTime of the last event.
|
|
||||||
///
|
|
||||||
/// \see lastEvent
|
|
||||||
[[nodiscard]] QDateTime lastActiveTime();
|
[[nodiscard]] QDateTime lastActiveTime();
|
||||||
|
|
||||||
/// Get subtitle text for room
|
/**
|
||||||
///
|
* @brief Get the last interesting event.
|
||||||
/// Fetches last event and removes markdown formatting
|
*
|
||||||
/// \see lastEventToString
|
* This function respects the user's state event setting and discards
|
||||||
|
* other not interesting events.
|
||||||
|
*
|
||||||
|
* @warning This function can return an empty pointer if the room does not have
|
||||||
|
* any RoomMessageEvents loaded.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] const Quotient::RoomEvent *lastEvent() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Output a string for the message content ready for display.
|
||||||
|
*
|
||||||
|
* The output string is dependant upon the event type and the desired output format.
|
||||||
|
*
|
||||||
|
* For most messages this is the body content of the message. For media messages
|
||||||
|
* This will be the caption and for state events it will be a string specific
|
||||||
|
* to that event with some dynamic details about the event added.
|
||||||
|
*
|
||||||
|
* E.g. For a room topic state event the text will be:
|
||||||
|
* "set the topic to: <new topic text>"
|
||||||
|
*
|
||||||
|
* @param evt the event for which a string is desired.
|
||||||
|
* @param format the output format, usually Qt::PlainText or Qt::RichText.
|
||||||
|
* @param stripNewlines whether the output should have new lines in it.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString eventToString(const Quotient::RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool stripNewlines = false) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Output a generic string for the message content ready for display.
|
||||||
|
*
|
||||||
|
* The output string is dependant upon the event type.
|
||||||
|
*
|
||||||
|
* Unlike NeoChatRoom::eventToString the string is the same for all events of
|
||||||
|
* the same type
|
||||||
|
*
|
||||||
|
* E.g. For a message the text will be:
|
||||||
|
* "sent a message"
|
||||||
|
*
|
||||||
|
* @sa eventToString()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString eventToGenericString(const Quotient::RoomEvent &evt) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convenient way to call eventToString on the last event.
|
||||||
|
*
|
||||||
|
* @sa lastEvent()
|
||||||
|
* @sa eventToString()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QString lastEventToString(Qt::TextFormat format = Qt::PlainText, bool stripNewlines = false) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convenient way to check if the last event looks like it has spoilers.
|
||||||
|
*
|
||||||
|
* This does a basic check to see if the message contains a data-mx-spoiler
|
||||||
|
* attribute within the text which makes it likely that the message has a spoiler
|
||||||
|
* section. However this is not 100% reliable as during parsing it may be
|
||||||
|
* removed if used within an illegal tag or on a tag for which data-mx-spoiler
|
||||||
|
* is not a valid attribute.
|
||||||
|
*
|
||||||
|
* @sa lastEvent()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool lastEventIsSpoiler() const;
|
||||||
|
|
||||||
|
[[nodiscard]] bool hasFileUploading() const;
|
||||||
|
void setHasFileUploading(bool value);
|
||||||
|
|
||||||
|
[[nodiscard]] int fileUploadingProgress() const;
|
||||||
|
void setFileUploadingProgress(int value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Download a file for the given event to a local file location.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void download(const QString &eventId, const QUrl &localFilename = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Download a file for the given event as a temporary file.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE bool downloadTempFile(const QString &eventId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the given event is highlighted.
|
||||||
|
*
|
||||||
|
* An event is highlighted if it contains the local user's id but was not sent by the
|
||||||
|
* local user.
|
||||||
|
*/
|
||||||
|
bool isEventHighlighted(const Quotient::RoomEvent *e) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convenience function to find out if the room contains the given user.
|
||||||
|
*
|
||||||
|
* A room contains the user if the user can be found and their JoinState is
|
||||||
|
* not JoinState::Leave.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] bool containsUser(const QString &userID) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief True if the given user ID is banned from the room.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] bool isUserBanned(const QString &user) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief True if the local user can send the given event type.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] bool canSendEvent(const QString &eventType) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief True if the local user can send the given state event type.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE [[nodiscard]] bool canSendState(const QString &eventType) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send a report to the server for an event.
|
||||||
|
*
|
||||||
|
* @param eventId the ID of the event being reported.
|
||||||
|
* @param reason the reason given for reporting the event.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void reportEvent(const QString &eventId, const QString &reason);
|
||||||
|
|
||||||
|
[[nodiscard]] bool readMarkerLoaded() const;
|
||||||
|
|
||||||
|
QString htmlSafeDisplayName() const;
|
||||||
|
|
||||||
|
#ifndef QUOTIENT_07
|
||||||
|
/**
|
||||||
|
* @brief Get a display name for the user with html escaped.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE QString htmlSafeMemberName(const QString &userId) const
|
||||||
|
{
|
||||||
|
return safeMemberName(userId).toHtmlEscaped();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get subtitle text for room
|
||||||
|
*
|
||||||
|
* Fetches last event and removes markdown formatting
|
||||||
|
*
|
||||||
|
* @see lastEventToString()
|
||||||
|
*/
|
||||||
[[nodiscard]] QString subtitleText();
|
[[nodiscard]] QString subtitleText();
|
||||||
|
|
||||||
|
[[nodiscard]] QString avatarMediaId() const;
|
||||||
|
|
||||||
|
NeoChatUser *directChatRemoteUser() const;
|
||||||
|
|
||||||
[[nodiscard]] bool isSpace();
|
[[nodiscard]] bool isSpace();
|
||||||
|
|
||||||
bool isEventHighlighted(const Quotient::RoomEvent *e) const;
|
bool isInvite() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void clearInvitationNotification();
|
||||||
|
|
||||||
[[nodiscard]] QString joinRule() const;
|
[[nodiscard]] QString joinRule() const;
|
||||||
void setJoinRule(const QString &joinRule);
|
void setJoinRule(const QString &joinRule);
|
||||||
|
|
||||||
|
int maxRoomVersion() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Map an alias to the room and publish.
|
||||||
|
*
|
||||||
|
* The alias is first mapped to the room and then published as an
|
||||||
|
* alternate alias. Publishing the alias will fail if the user does not have
|
||||||
|
* permission to send m.room.canonical_alias event messages.
|
||||||
|
*
|
||||||
|
* @note This is different to Quotient::Room::setLocalAliases() as that can only
|
||||||
|
* get the room to publish an alias that is already mapped.
|
||||||
|
*
|
||||||
|
* @property alias QString in the form #new_alias:server.org
|
||||||
|
*
|
||||||
|
* @sa Quotient::Room::setLocalAliases()
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void mapAlias(const QString &alias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmap an alias from the room.
|
||||||
|
*
|
||||||
|
* An unmapped alias is also removed as either the canonical alias or an alternate
|
||||||
|
* alias.
|
||||||
|
*
|
||||||
|
* @note This is different to Quotient::Room::setLocalAliases() as that can only
|
||||||
|
* get the room to un-publish an alias, while the mapping still exists.
|
||||||
|
*
|
||||||
|
* @property alias QString in the form #mapped_alias:server.org
|
||||||
|
*
|
||||||
|
* @sa Quotient::Room::setLocalAliases()
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void unmapAlias(const QString &alias);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the canonical alias of the room to an available mapped alias.
|
||||||
|
*
|
||||||
|
* If the new alias was already published as an alternate alias it will be removed
|
||||||
|
* from that list.
|
||||||
|
*
|
||||||
|
* @note This is an overload of the function Quotient::Room::setCanonicalAlias().
|
||||||
|
* This is to provide the functionality to remove the new canonical alias as a
|
||||||
|
* published alt alias when set.
|
||||||
|
*
|
||||||
|
* @property newAlias QString in the form #new_alias:server.org
|
||||||
|
*
|
||||||
|
* @sa Quotient::Room::setCanonicalAlias()
|
||||||
|
* */
|
||||||
|
Q_INVOKABLE void setCanonicalAlias(const QString &newAlias);
|
||||||
|
|
||||||
|
PushNotificationState::State pushNotificationState() const;
|
||||||
|
void setPushNotificationState(PushNotificationState::State state);
|
||||||
|
|
||||||
[[nodiscard]] QString historyVisibility() const;
|
[[nodiscard]] QString historyVisibility() const;
|
||||||
void setHistoryVisibility(const QString &historyVisibilityRule);
|
void setHistoryVisibility(const QString &historyVisibilityRule);
|
||||||
|
|
||||||
|
[[nodiscard]] bool defaultUrlPreviewState() const;
|
||||||
|
void setDefaultUrlPreviewState(const bool &defaultUrlPreviewState);
|
||||||
|
|
||||||
|
[[nodiscard]] bool urlPreviewEnabled() const;
|
||||||
|
void setUrlPreviewEnabled(const bool &urlPreviewEnabled);
|
||||||
|
|
||||||
|
bool canEncryptRoom() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the power level for the given user ID in the room.
|
* @brief Get the power level for the given user ID in the room.
|
||||||
*
|
*
|
||||||
@@ -223,65 +694,6 @@ public:
|
|||||||
[[nodiscard]] int spaceParentPowerLevel() const;
|
[[nodiscard]] int spaceParentPowerLevel() const;
|
||||||
void setSpaceParentPowerLevel(const int &newPowerLevel);
|
void setSpaceParentPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
[[nodiscard]] bool hasFileUploading() const
|
|
||||||
{
|
|
||||||
return m_hasFileUploading;
|
|
||||||
}
|
|
||||||
void setHasFileUploading(bool value)
|
|
||||||
{
|
|
||||||
if (value == m_hasFileUploading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_hasFileUploading = value;
|
|
||||||
Q_EMIT hasFileUploadingChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] int fileUploadingProgress() const
|
|
||||||
{
|
|
||||||
return m_fileUploadingProgress;
|
|
||||||
}
|
|
||||||
void setFileUploadingProgress(int value)
|
|
||||||
{
|
|
||||||
if (m_fileUploadingProgress == value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_fileUploadingProgress = value;
|
|
||||||
Q_EMIT fileUploadingProgressChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool readMarkerLoaded() const;
|
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] int savedTopVisibleIndex() const;
|
|
||||||
Q_INVOKABLE [[nodiscard]] int savedBottomVisibleIndex() const;
|
|
||||||
Q_INVOKABLE void saveViewport(int topIndex, int bottomIndex);
|
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] QVariantList getUsers(const QString &keyword, int limit = -1) const;
|
|
||||||
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const;
|
|
||||||
|
|
||||||
Q_INVOKABLE QUrl urlToMxcUrl(const QUrl &mxcUrl);
|
|
||||||
|
|
||||||
[[nodiscard]] QString avatarMediaId() const;
|
|
||||||
|
|
||||||
[[nodiscard]] QString eventToString(const Quotient::RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool removeReply = true) const;
|
|
||||||
[[nodiscard]] QString eventToGenericString(const Quotient::RoomEvent &evt) const;
|
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] bool containsUser(const QString &userID) const;
|
|
||||||
Q_INVOKABLE [[nodiscard]] bool isUserBanned(const QString &user) const;
|
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] bool canSendEvent(const QString &eventType) const;
|
|
||||||
Q_INVOKABLE [[nodiscard]] bool canSendState(const QString &eventType) const;
|
|
||||||
|
|
||||||
bool isInvite() const;
|
|
||||||
|
|
||||||
Q_INVOKABLE QString htmlSafeName() const;
|
|
||||||
Q_INVOKABLE QString htmlSafeDisplayName() const;
|
|
||||||
Q_INVOKABLE void clearInvitationNotification();
|
|
||||||
Q_INVOKABLE void reportEvent(const QString &eventId, const QString &reason);
|
|
||||||
|
|
||||||
Q_INVOKABLE void setPushNotificationState(PushNotificationState::State state);
|
|
||||||
|
|
||||||
Q_INVOKABLE void download(const QString &eventId, const QUrl &localFilename = {});
|
|
||||||
|
|
||||||
QString chatBoxText() const;
|
QString chatBoxText() const;
|
||||||
void setChatBoxText(const QString &text);
|
void setChatBoxText(const QString &text);
|
||||||
|
|
||||||
@@ -303,43 +715,37 @@ public:
|
|||||||
QString chatBoxAttachmentPath() const;
|
QString chatBoxAttachmentPath() const;
|
||||||
void setChatBoxAttachmentPath(const QString &attachmentPath);
|
void setChatBoxAttachmentPath(const QString &attachmentPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieve the mentions for the current chatbox text.
|
||||||
|
*/
|
||||||
QVector<Mention> *mentions();
|
QVector<Mention> *mentions();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Vector of mentions in the current edit text.
|
* @brief Retrieve the mentions for the current edit text.
|
||||||
*/
|
*/
|
||||||
QVector<Mention> *editMentions();
|
QVector<Mention> *editMentions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the saved chatbox text for the room.
|
||||||
|
*/
|
||||||
QString savedText() const;
|
QString savedText() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Save the chatbox text for the room.
|
||||||
|
*/
|
||||||
void setSavedText(const QString &savedText);
|
void setSavedText(const QString &savedText);
|
||||||
|
|
||||||
bool canEncryptRoom() const;
|
/**
|
||||||
|
* @brief Get a PollHandler object for the given event Id.
|
||||||
Q_INVOKABLE bool downloadTempFile(const QString &eventId);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Map an alias to the room
|
|
||||||
*
|
*
|
||||||
* Note: this is different to setLocalAliases as that can only
|
* Will return an existing PollHandler if one already exists for the event ID.
|
||||||
* get the room to publish and alias that is already mapped.
|
* A new PollHandler will be created if one doesn't exist.
|
||||||
|
*
|
||||||
|
* @note Requires libQuotient 0.7.
|
||||||
|
*
|
||||||
|
* @sa PollHandler
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void mapAlias(const QString &alias);
|
|
||||||
Q_INVOKABLE void unmapAlias(const QString &alias);
|
|
||||||
Q_INVOKABLE void setCanonicalAlias(const QString &newAlias);
|
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
Q_INVOKABLE PollHandler *poll(const QString &eventId);
|
Q_INVOKABLE PollHandler *poll(const QString &eventId);
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef QUOTIENT_07
|
|
||||||
Q_INVOKABLE QString htmlSafeMemberName(const QString &userId) const
|
|
||||||
{
|
|
||||||
return safeMemberName(userId).toHtmlEscaped();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int maxRoomVersion() const;
|
|
||||||
NeoChatUser *directChatRemoteUser() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSet<const Quotient::RoomEvent *> highlights;
|
QSet<const Quotient::RoomEvent *> highlights;
|
||||||
@@ -367,9 +773,7 @@ private:
|
|||||||
QVector<Mention> m_mentions;
|
QVector<Mention> m_mentions;
|
||||||
QVector<Mention> m_editMentions;
|
QVector<Mention> m_editMentions;
|
||||||
QString m_savedText;
|
QString m_savedText;
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
QCache<QString, PollHandler> m_polls;
|
QCache<QString, PollHandler> m_polls;
|
||||||
#endif
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void countChanged();
|
void countChanged();
|
||||||
@@ -395,6 +799,8 @@ Q_SIGNALS:
|
|||||||
void canEncryptRoomChanged();
|
void canEncryptRoomChanged();
|
||||||
void joinRuleChanged();
|
void joinRuleChanged();
|
||||||
void historyVisibilityChanged();
|
void historyVisibilityChanged();
|
||||||
|
void defaultUrlPreviewStateChanged();
|
||||||
|
void urlPreviewEnabledChanged();
|
||||||
void maxRoomVersionChanged();
|
void maxRoomVersionChanged();
|
||||||
void defaultUserPowerLevelChanged();
|
void defaultUserPowerLevelChanged();
|
||||||
void invitePowerLevelChanged();
|
void invitePowerLevelChanged();
|
||||||
@@ -417,26 +823,78 @@ Q_SIGNALS:
|
|||||||
void spaceParentPowerLevelChanged();
|
void spaceParentPowerLevelChanged();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
|
/**
|
||||||
|
* @brief Upload a file to the matrix server and post the file to the room.
|
||||||
|
*
|
||||||
|
* @param url the location of the file to be uploaded.
|
||||||
|
* @param body the caption that is to be given to the file.
|
||||||
|
*/
|
||||||
void uploadFile(const QUrl &url, const QString &body = QString());
|
void uploadFile(const QUrl &url, const QString &body = QString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Accept an invitation for the local user to join the room.
|
||||||
|
*/
|
||||||
void acceptInvitation();
|
void acceptInvitation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Leave and forget the room for the local user.
|
||||||
|
*
|
||||||
|
* @note This means that not only will the user no longer receive events in
|
||||||
|
* the room but the will forget any history up to this point.
|
||||||
|
*
|
||||||
|
* @sa https://spec.matrix.org/latest/client-server-api/#leaving-rooms
|
||||||
|
*/
|
||||||
void forget();
|
void forget();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the typing notification state on the room for the local user.
|
||||||
|
*/
|
||||||
void sendTypingNotification(bool isTyping);
|
void sendTypingNotification(bool isTyping);
|
||||||
|
|
||||||
/// @param rawText The text as it was typed.
|
/**
|
||||||
/// @param cleanedText The text with link to the users.
|
* @brief Send a message to the room.
|
||||||
|
*
|
||||||
|
* @param rawText the text as it was typed.
|
||||||
|
* @param cleanedText the text marked up as html.
|
||||||
|
* @param type the type of message being sent.
|
||||||
|
* @param replyEventId the id of the message being replied to if a reply.
|
||||||
|
* @param relateToEventId the id of the message being edited if an edit.
|
||||||
|
*/
|
||||||
void postMessage(const QString &rawText,
|
void postMessage(const QString &rawText,
|
||||||
const QString &cleanedText,
|
const QString &cleanedText,
|
||||||
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
||||||
const QString &replyEventId = QString(),
|
const QString &replyEventId = QString(),
|
||||||
const QString &relateToEventId = QString());
|
const QString &relateToEventId = QString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Send an html message to the room.
|
||||||
|
*
|
||||||
|
* @param text the text as it was typed.
|
||||||
|
* @param html the text marked up as html.
|
||||||
|
* @param type the type of message being sent.
|
||||||
|
* @param replyEventId the id of the message being replied to if a reply.
|
||||||
|
* @param relateToEventId the id of the message being edited if an edit.
|
||||||
|
*/
|
||||||
void postHtmlMessage(const QString &text,
|
void postHtmlMessage(const QString &text,
|
||||||
const QString &html,
|
const QString &html,
|
||||||
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
||||||
const QString &replyEventId = QString(),
|
const QString &replyEventId = QString(),
|
||||||
const QString &relateToEventId = QString());
|
const QString &relateToEventId = QString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the room avatar.
|
||||||
|
*/
|
||||||
void changeAvatar(const QUrl &localFile);
|
void changeAvatar(const QUrl &localFile);
|
||||||
void addLocalAlias(const QString &alias);
|
|
||||||
void removeLocalAlias(const QString &alias);
|
/**
|
||||||
|
* @brief Toggle the reaction state of the given reaction for the local user.
|
||||||
|
*/
|
||||||
void toggleReaction(const QString &eventId, const QString &reaction);
|
void toggleReaction(const QString &eventId, const QString &reaction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete recent messages by the given user.
|
||||||
|
*
|
||||||
|
* This will delete all messages by that user in this room that are currently loaded.
|
||||||
|
*/
|
||||||
void deleteMessagesByUser(const QString &user, const QString &reason);
|
void deleteMessagesByUser(const QString &user, const QString &reason);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,22 +11,18 @@
|
|||||||
#include <KNotification>
|
#include <KNotification>
|
||||||
#include <KNotificationReplyAction>
|
#include <KNotificationReplyAction>
|
||||||
|
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
#include <accountregistry.h>
|
#include <accountregistry.h>
|
||||||
#else
|
|
||||||
#include "neochataccountregistry.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <connection.h>
|
#include <connection.h>
|
||||||
#include <csapi/pushrules.h>
|
#include <csapi/pushrules.h>
|
||||||
#include <jobs/basejob.h>
|
#include <jobs/basejob.h>
|
||||||
#include <user.h>
|
#include <user.h>
|
||||||
|
|
||||||
#include "actionshandler.h"
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
|
#include "texthandler.h"
|
||||||
#include "windowcontroller.h"
|
#include "windowcontroller.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -72,11 +68,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
|||||||
connect(notification, &KNotification::defaultActivated, this, [=]() {
|
connect(notification, &KNotification::defaultActivated, this, [=]() {
|
||||||
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
||||||
if (room->localUser()->id() != Controller::instance().activeConnection()->userId()) {
|
if (room->localUser()->id() != Controller::instance().activeConnection()->userId()) {
|
||||||
#ifdef QUOTIENT_07
|
|
||||||
Controller::instance().setActiveConnection(Accounts.get(room->localUser()->id()));
|
Controller::instance().setActiveConnection(Accounts.get(room->localUser()->id()));
|
||||||
#else
|
|
||||||
Controller::instance().setActiveConnection(AccountRegistry::instance().get(room->localUser()->id()));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
RoomManager::instance().enterRoom(room);
|
RoomManager::instance().enterRoom(room);
|
||||||
});
|
});
|
||||||
@@ -85,7 +77,9 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
|||||||
std::unique_ptr<KNotificationReplyAction> replyAction(new KNotificationReplyAction(i18n("Reply")));
|
std::unique_ptr<KNotificationReplyAction> replyAction(new KNotificationReplyAction(i18n("Reply")));
|
||||||
replyAction->setPlaceholderText(i18n("Reply..."));
|
replyAction->setPlaceholderText(i18n("Reply..."));
|
||||||
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
|
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
|
||||||
room->postMessage(text, markdownToHTML(text), RoomMessageEvent::MsgType::Text, replyEventId, QString());
|
TextHandler textHandler;
|
||||||
|
textHandler.setData(text);
|
||||||
|
room->postMessage(text, textHandler.handleSendText(), RoomMessageEvent::MsgType::Text, replyEventId, QString());
|
||||||
});
|
});
|
||||||
notification->setReplyAction(std::move(replyAction));
|
notification->setReplyAction(std::move(replyAction));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ void PollHandler::setRoom(NeoChatRoom *room)
|
|||||||
connect(room, &NeoChatRoom::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
|
connect(room, &NeoChatRoom::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
|
||||||
for (const auto &event : events) {
|
for (const auto &event : events) {
|
||||||
if (event->is<PollEndEvent>()) {
|
if (event->is<PollEndEvent>()) {
|
||||||
auto pl = m_room->getCurrentState<RoomPowerLevelsEvent>();
|
auto pl = m_room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
auto userPl = pl->powerLevelForUser(event->senderId());
|
auto userPl = pl->powerLevelForUser(event->senderId());
|
||||||
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= pl->redact()) {
|
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= pl->redact()) {
|
||||||
m_hasEnded = true;
|
m_hasEnded = true;
|
||||||
@@ -75,7 +75,7 @@ void PollHandler::checkLoadRelations()
|
|||||||
connect(job, &BaseJob::success, this, [this, job]() {
|
connect(job, &BaseJob::success, this, [this, job]() {
|
||||||
for (const auto &event : job->chunk()) {
|
for (const auto &event : job->chunk()) {
|
||||||
if (event->is<PollEndEvent>()) {
|
if (event->is<PollEndEvent>()) {
|
||||||
auto pl = m_room->getCurrentState<RoomPowerLevelsEvent>();
|
auto pl = m_room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
auto userPl = pl->powerLevelForUser(event->senderId());
|
auto userPl = pl->powerLevelForUser(event->senderId());
|
||||||
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= pl->redact()) {
|
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= pl->redact()) {
|
||||||
m_hasEnded = true;
|
m_hasEnded = true;
|
||||||
|
|||||||
@@ -139,11 +139,41 @@ QQC2.Control {
|
|||||||
currentRoom.chatBoxText = text
|
currentRoom.chatBoxText = text
|
||||||
}
|
}
|
||||||
onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle)
|
onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle)
|
||||||
|
onSelectedTextChanged: {
|
||||||
|
if (selectedText.length > 0) {
|
||||||
|
quickFormatBar.selectionStart = selectionStart
|
||||||
|
quickFormatBar.selectionEnd = selectionEnd
|
||||||
|
quickFormatBar.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QuickFormatBar {
|
||||||
|
id: quickFormatBar
|
||||||
|
|
||||||
|
x: textField.cursorRectangle.x
|
||||||
|
y: textField.cursorRectangle.y - height
|
||||||
|
|
||||||
|
onFormattingSelected: chatBar.formatText(format, selectionStart, selectionEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onDeletePressed: {
|
||||||
|
if (selectedText.length > 0) {
|
||||||
|
remove(selectionStart, selectionEnd)
|
||||||
|
} else {
|
||||||
|
remove(cursorPosition, cursorPosition + 1)
|
||||||
|
}
|
||||||
|
if (textField.text == selectedText || textField.text.length <= 1) {
|
||||||
|
currentRoom.sendTypingNotification(false)
|
||||||
|
repeatTimer.stop()
|
||||||
|
}
|
||||||
|
if (quickFormatBar.visible) {
|
||||||
|
quickFormatBar.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
Keys.onEnterPressed: {
|
Keys.onEnterPressed: {
|
||||||
if (completionMenu.visible) {
|
if (completionMenu.visible) {
|
||||||
completionMenu.complete()
|
completionMenu.complete()
|
||||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile) {
|
||||||
textField.insert(cursorPosition, "\n")
|
textField.insert(cursorPosition, "\n")
|
||||||
} else {
|
} else {
|
||||||
chatBar.postMessage();
|
chatBar.postMessage();
|
||||||
@@ -152,7 +182,7 @@ QQC2.Control {
|
|||||||
Keys.onReturnPressed: {
|
Keys.onReturnPressed: {
|
||||||
if (completionMenu.visible) {
|
if (completionMenu.visible) {
|
||||||
completionMenu.complete()
|
completionMenu.complete()
|
||||||
} else if (event.modifiers & Qt.ShiftModifier) {
|
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile) {
|
||||||
textField.insert(cursorPosition, "\n")
|
textField.insert(cursorPosition, "\n")
|
||||||
} else {
|
} else {
|
||||||
chatBar.postMessage();
|
chatBar.postMessage();
|
||||||
@@ -167,7 +197,7 @@ QQC2.Control {
|
|||||||
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
||||||
chatBar.pasteImage();
|
chatBar.pasteImage();
|
||||||
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
|
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
|
||||||
let replyEvent = messageEventModel.getLatestMessageFromIndex(0)
|
let replyEvent = messageEventModel.getLatestMessageFromRow(0)
|
||||||
if (replyEvent && replyEvent["event_id"]) {
|
if (replyEvent && replyEvent["event_id"]) {
|
||||||
currentRoom.chatBoxReplyId = replyEvent["event_id"]
|
currentRoom.chatBoxReplyId = replyEvent["event_id"]
|
||||||
}
|
}
|
||||||
@@ -180,9 +210,14 @@ QQC2.Control {
|
|||||||
completionMenu.decrementIndex()
|
completionMenu.decrementIndex()
|
||||||
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
|
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
|
||||||
completionMenu.incrementIndex()
|
completionMenu.incrementIndex()
|
||||||
} else if (event.key === Qt.Key_Backspace && textField.text.length <= 1) {
|
} else if (event.key === Qt.Key_Backspace) {
|
||||||
currentRoom.sendTypingNotification(false)
|
if (textField.text == selectedText || textField.text.length <= 1) {
|
||||||
repeatTimer.stop()
|
currentRoom.sendTypingNotification(false)
|
||||||
|
repeatTimer.stop()
|
||||||
|
}
|
||||||
|
if (quickFormatBar.visible && selectedText.length > 0) {
|
||||||
|
quickFormatBar.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keys.onShortcutOverride: {
|
Keys.onShortcutOverride: {
|
||||||
@@ -313,14 +348,9 @@ QQC2.Control {
|
|||||||
QQC2.ToolTip.text: modelData.tooltip
|
QQC2.ToolTip.text: modelData.tooltip
|
||||||
HoverHandler { id: hoverHandler }
|
HoverHandler { id: hoverHandler }
|
||||||
|
|
||||||
QQC2.BusyIndicator {
|
PieProgressBar {
|
||||||
anchors.fill: parent
|
visible: modelData.isBusy
|
||||||
leftPadding: 0
|
progress: currentRoom.fileUploadingProgress
|
||||||
rightPadding: 0
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
visible: running
|
|
||||||
running: modelData.isBusy
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,6 +394,8 @@ QQC2.Control {
|
|||||||
cursorPosition: textField.cursorPosition
|
cursorPosition: textField.cursorPosition
|
||||||
selectionStart: textField.selectionStart
|
selectionStart: textField.selectionStart
|
||||||
selectionEnd: textField.selectionEnd
|
selectionEnd: textField.selectionEnd
|
||||||
|
mentionColor: Kirigami.Theme.linkColor
|
||||||
|
errorColor: Kirigami.Theme.negativeTextColor
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
RoomManager.chatDocumentHandler = documentHandler;
|
RoomManager.chatDocumentHandler = documentHandler;
|
||||||
}
|
}
|
||||||
@@ -398,4 +430,47 @@ QQC2.Control {
|
|||||||
currentRoom.chatBoxReplyId = "";
|
currentRoom.chatBoxReplyId = "";
|
||||||
messageSent()
|
messageSent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatText(format, selectionStart, selectionEnd) {
|
||||||
|
let index = textField.cursorPosition;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There cannot be white space at the beginning or end of the string for the
|
||||||
|
* formatting to work so move the sectionStart and sectionEnd markers past any whitespace.
|
||||||
|
*/
|
||||||
|
let innerText = textField.text.substr(selectionStart, selectionEnd - selectionStart);
|
||||||
|
if (innerText.charAt(innerText.length - 1) === " ") {
|
||||||
|
let trimmedRightString = innerText.replace(/\s*$/,"");
|
||||||
|
let trimDifference = innerText.length - trimmedRightString.length;
|
||||||
|
selectionEnd -= trimDifference;
|
||||||
|
}
|
||||||
|
if (innerText.charAt(0) === " ") {
|
||||||
|
let trimmedLeftString = innerText.replace(/^\s*/,"");
|
||||||
|
let trimDifference = innerText.length - trimmedLeftString.length;
|
||||||
|
selectionStart = selectionStart + trimDifference;
|
||||||
|
}
|
||||||
|
|
||||||
|
let startText = textField.text.substr(0, selectionStart);
|
||||||
|
// Needs updating with the new selectionStart and selectionEnd with white space trimmed.
|
||||||
|
innerText = textField.text.substr(selectionStart, selectionEnd - selectionStart);
|
||||||
|
let endText = textField.text.substr(selectionEnd);
|
||||||
|
|
||||||
|
textField.text = "";
|
||||||
|
textField.text = startText + format.start + innerText + format.end + format.extra + endText;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Put the cursor where it was when the popup was opened accounting for the
|
||||||
|
* new markup.
|
||||||
|
*
|
||||||
|
* The exception is for a hyperlink where it is placed ready to start typing
|
||||||
|
* the url.
|
||||||
|
*/
|
||||||
|
if (format.extra !== "") {
|
||||||
|
textField.cursorPosition = selectionEnd + format.start.length + format.end.length;
|
||||||
|
} else if (index == selectionStart) {
|
||||||
|
textField.cursorPosition = index;
|
||||||
|
} else {
|
||||||
|
textField.cursorPosition = index + format.start.length + format.end.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
65
src/qml/Component/ChatBox/PieProgressBar.qml
Normal file
65
src/qml/Component/ChatBox/PieProgressBar.qml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.18 as Kirigami
|
||||||
|
import org.kde.quickcharts 1.0 as Charts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A circular progress bar that fills an arc as progress goes up.
|
||||||
|
*/
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Progress of the circle as a percentage.
|
||||||
|
*
|
||||||
|
* Range - 0% to 100%.
|
||||||
|
*/
|
||||||
|
property int progress: 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Offset angle for the start of the pie fill arc.
|
||||||
|
*
|
||||||
|
* This defaults to 0, i.e. an upward vertical line from the center. This rotates
|
||||||
|
* that start point by the desired number of degrees.
|
||||||
|
*
|
||||||
|
* Range - 0 degrees to 360 degrees
|
||||||
|
*/
|
||||||
|
property int startOffset: 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fill color of the pie.
|
||||||
|
*/
|
||||||
|
property color pieColor: Kirigami.Theme.highlightColor
|
||||||
|
|
||||||
|
width: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
height: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
radius: width / 2
|
||||||
|
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.15)
|
||||||
|
|
||||||
|
Charts.PieChart {
|
||||||
|
id: chart
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 1
|
||||||
|
|
||||||
|
filled: true
|
||||||
|
// Set chart background color so the parent filled rectangle looks like
|
||||||
|
// an outline.
|
||||||
|
backgroundColor: Kirigami.Theme.backgroundColor
|
||||||
|
fromAngle: root.startOffset
|
||||||
|
toAngle: 360 + root.startOffset
|
||||||
|
range {
|
||||||
|
from: 0
|
||||||
|
to: 100
|
||||||
|
automatic: false
|
||||||
|
}
|
||||||
|
valueSources: Charts.SingleValueSource {
|
||||||
|
value: root.progress
|
||||||
|
}
|
||||||
|
colorSource: Charts.SingleValueSource {
|
||||||
|
value: Kirigami.Theme.highlightColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
src/qml/Component/ChatBox/QuickFormatBar.qml
Normal file
136
src/qml/Component/ChatBox/QuickFormatBar.qml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
QQC2.Popup {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var selectionStart
|
||||||
|
property var selectionEnd
|
||||||
|
|
||||||
|
signal formattingSelected(var format, int selectionStart, int selectionEnd)
|
||||||
|
|
||||||
|
padding: 1
|
||||||
|
|
||||||
|
contentItem: Flow {
|
||||||
|
QQC2.ToolButton {
|
||||||
|
icon.name: "format-text-bold"
|
||||||
|
text: i18n("Bold")
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
const format = {
|
||||||
|
start: "**",
|
||||||
|
end: "**",
|
||||||
|
extra: "",
|
||||||
|
}
|
||||||
|
formattingSelected(format, selectionStart, selectionEnd)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
icon.name: "format-text-italic"
|
||||||
|
text: i18n("Italic")
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
const format = {
|
||||||
|
start: "*",
|
||||||
|
end: "*",
|
||||||
|
extra: "",
|
||||||
|
}
|
||||||
|
formattingSelected(format, selectionStart, selectionEnd)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
icon.name: "format-text-strikethrough"
|
||||||
|
text: i18n("Strikethrough")
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
const format = {
|
||||||
|
start: "<del>",
|
||||||
|
end: "</del>",
|
||||||
|
extra: "",
|
||||||
|
}
|
||||||
|
formattingSelected(format, selectionStart, selectionEnd)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
icon.name: "format-text-code"
|
||||||
|
text: i18n("Code block")
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
const format = {
|
||||||
|
start: "`",
|
||||||
|
end: "`",
|
||||||
|
extra: "",
|
||||||
|
}
|
||||||
|
formattingSelected(format, selectionStart, selectionEnd)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
icon.name: "format-text-blockquote"
|
||||||
|
text: i18n("Quote")
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
const format = {
|
||||||
|
start: selectionStart == 0 ? ">" : "\n>",
|
||||||
|
end: "\n\n",
|
||||||
|
extra: "",
|
||||||
|
}
|
||||||
|
formattingSelected(format, selectionStart, selectionEnd)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
icon.name: "link"
|
||||||
|
text: i18n("Insert link")
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
const format = {
|
||||||
|
start: "[",
|
||||||
|
end: "](",
|
||||||
|
extra: ")",
|
||||||
|
}
|
||||||
|
formattingSelected(format, selectionStart, selectionEnd)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,115 +1,132 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Layouts 1.10
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
import QtQuick.Layouts 1.15
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
import org.kde.kitemmodels 1.0
|
import org.kde.kitemmodels 1.0
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
QQC2.Popup {
|
QQC2.Dialog {
|
||||||
id: _popup
|
id: root
|
||||||
|
|
||||||
|
parent: applicationWindow().overlay
|
||||||
|
width: Math.min(700, parent.width)
|
||||||
|
height: 400
|
||||||
|
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
topPadding: 0
|
||||||
|
|
||||||
|
anchors.centerIn: applicationWindow().overlay
|
||||||
|
|
||||||
|
Keys.forwardTo: searchField
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+K"
|
sequence: "Ctrl+K"
|
||||||
enabled: !Kirigami.Settings.hasPlatformMenuBar
|
onActivated: root.open()
|
||||||
onActivated: _popup.open()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
quickSearch.forceActiveFocus()
|
searchField.forceActiveFocus()
|
||||||
quickSearch.text = ""
|
searchField.text = ""
|
||||||
|
roomList.currentIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
anchors.centerIn: QQC2.Overlay.overlay
|
header: Kirigami.SearchField {
|
||||||
background: Kirigami.Card {}
|
id: searchField
|
||||||
height: 2 * Math.round(implicitHeight / 2)
|
Keys.onDownPressed: {
|
||||||
padding: Kirigami.Units.largeSpacing * 2
|
roomList.forceActiveFocus()
|
||||||
|
if (roomList.currentIndex < roomList.count - 1) {
|
||||||
contentItem: ColumnLayout {
|
roomList.currentIndex++
|
||||||
spacing: Kirigami.Units.largeSpacing * 2
|
} else {
|
||||||
|
roomList.currentIndex = 0
|
||||||
Kirigami.SearchField {
|
|
||||||
id: quickSearch
|
|
||||||
|
|
||||||
// TODO: get this broken property removed/disabled by default in Kirigami,
|
|
||||||
// we used to be able to expect that the text field wouldn't attempt to
|
|
||||||
// perform a mini-DDOS attack using signals.
|
|
||||||
autoAccept: false
|
|
||||||
/**
|
|
||||||
* The focus is manged by the popup and we don't want to use the standard
|
|
||||||
* shortcut as it could block other SearchFields from using it.
|
|
||||||
*/
|
|
||||||
focusSequence: ""
|
|
||||||
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen
|
|
||||||
Keys.onLeftPressed: cView.decrementCurrentIndex()
|
|
||||||
Keys.onRightPressed: cView.incrementCurrentIndex()
|
|
||||||
onAccepted: {
|
|
||||||
const item = cView.itemAtIndex(cView.currentIndex)
|
|
||||||
|
|
||||||
RoomManager.enterRoom(item.currentRoom)
|
|
||||||
|
|
||||||
_popup.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Keys.onUpPressed: {
|
||||||
|
if (roomList.currentIndex === 0) {
|
||||||
|
roomList.currentIndex = roomList.count - 1
|
||||||
|
} else {
|
||||||
|
roomList.currentIndex--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keys.onEnterPressed: {
|
||||||
|
RoomManager.enterRoom(roomList.currentItem.currentRoom);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
RoomManager.enterRoom(roomList.currentItem.currentRoom);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ScrollView {
|
||||||
|
anchors.fill: parent
|
||||||
ListView {
|
ListView {
|
||||||
id: cView
|
id: roomList
|
||||||
|
|
||||||
orientation: Qt.Horizontal
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
|
currentIndex: 0
|
||||||
|
highlightMoveDuration: 200
|
||||||
|
Keys.forwardTo: searchField
|
||||||
|
keyNavigationEnabled: true
|
||||||
model: SortFilterRoomListModel {
|
model: SortFilterRoomListModel {
|
||||||
id: sortFilterRoomListModel
|
filterText: searchField.text
|
||||||
sourceModel: RoomListModel {
|
sourceModel: RoomListModel {
|
||||||
id: roomListModel
|
id: roomListModel
|
||||||
connection: Controller.activeConnection
|
connection: Controller.activeConnection
|
||||||
}
|
}
|
||||||
filterText: quickSearch.text
|
|
||||||
roomSortOrder: SortFilterRoomListModel.LastActivity
|
|
||||||
}
|
}
|
||||||
|
delegate: Kirigami.BasicListItem {
|
||||||
|
id: roomListItem
|
||||||
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
delegate: Kirigami.Avatar {
|
|
||||||
id: del
|
|
||||||
|
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 3
|
|
||||||
implicitWidth: Kirigami.Units.gridUnit * 3
|
|
||||||
|
|
||||||
required property string avatar
|
|
||||||
required property var currentRoom
|
required property var currentRoom
|
||||||
|
required property string name
|
||||||
required property int index
|
required property int index
|
||||||
|
required property int unreadCount
|
||||||
|
required property string subtitleText
|
||||||
|
required property string avatar
|
||||||
|
|
||||||
name: currentRoom.displayName
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
// When an item is hovered set the currentIndex of listview to it so that it is highlighted
|
highlighted: roomList.currentIndex === roomListItem.index
|
||||||
onHoveredChanged: {
|
focus: true
|
||||||
if (!hovered) {
|
icon: undefined
|
||||||
return
|
onClicked: {
|
||||||
}
|
RoomManager.enterRoom(roomListItem.currentRoom);
|
||||||
cView.currentIndex = index
|
root.close()
|
||||||
|
}
|
||||||
|
Keys.onEnterPressed: {
|
||||||
|
RoomManager.enterRoom(roomListItem.currentRoom);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
RoomManager.enterRoom(roomListItem.currentRoom);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
bold: roomListItem.unreadCount > 0
|
||||||
|
label: roomListItem.name ?? ""
|
||||||
|
labelItem.textFormat: Text.PlainText
|
||||||
|
subtitle: roomListItem.subtitleText
|
||||||
|
subtitleItem.textFormat: Text.PlainText
|
||||||
|
onPressAndHold: {
|
||||||
|
createRoomListContextMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.main: Kirigami.Action {
|
leading: Kirigami.Avatar {
|
||||||
id: enterRoomAction
|
source: roomListItem.avatar ? "image://mxc/" + roomListItem.avatar : ""
|
||||||
onTriggered: {
|
name: roomListItem.name || i18n("No Name")
|
||||||
RoomManager.enterRoom(currentRoom);
|
implicitWidth: height
|
||||||
|
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
_popup.close()
|
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
source: avatar != "" ? "image://mxc/" + avatar : ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modal: true
|
|
||||||
focus: true
|
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/qml/Component/Timeline/AvatarFlow.qml
Normal file
37
src/qml/Component/Timeline/AvatarFlow.qml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var avatarSize: Kirigami.Units.iconSizes.small
|
||||||
|
property alias model: avatarFlowRepeater.model
|
||||||
|
property string toolTipText
|
||||||
|
|
||||||
|
spacing: -avatarSize / 2
|
||||||
|
Repeater {
|
||||||
|
id: avatarFlowRepeater
|
||||||
|
delegate: Kirigami.Avatar {
|
||||||
|
implicitWidth: avatarSize
|
||||||
|
implicitHeight: avatarSize
|
||||||
|
|
||||||
|
name: modelData.displayName
|
||||||
|
source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : ""
|
||||||
|
color: modelData.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: toolTipText
|
||||||
|
QQC2.ToolTip.visible: hoverHandler.hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: hoverHandler
|
||||||
|
margin: Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import org.kde.kirigami 2.15 as Kirigami
|
|||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
DelegateChooser {
|
DelegateChooser {
|
||||||
role: "eventType"
|
role: "delegateType"
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: MessageEventModel.State
|
roleValue: MessageEventModel.State
|
||||||
@@ -20,9 +20,7 @@ DelegateChooser {
|
|||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: MessageEventModel.Emote
|
roleValue: MessageEventModel.Emote
|
||||||
delegate: MessageDelegate {
|
delegate: MessageDelegate {}
|
||||||
isEmote: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
@@ -75,6 +73,11 @@ DelegateChooser {
|
|||||||
delegate: PollDelegate {}
|
delegate: PollDelegate {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: MessageEventModel.Location
|
||||||
|
delegate: LocationDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: MessageEventModel.Other
|
roleValue: MessageEventModel.Other
|
||||||
delegate: Item {}
|
delegate: Item {}
|
||||||
|
|||||||
@@ -110,14 +110,18 @@ TimelineContainer {
|
|||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
text: model.display
|
text: model.display
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: sizeLabel
|
id: sizeLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
|
elide: Text.ElideRight
|
||||||
|
maximumLineCount: 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Loader {
|
|||||||
*/
|
*/
|
||||||
property bool indicatorEnabled: false
|
property bool indicatorEnabled: false
|
||||||
|
|
||||||
active: !currentRoom.usesEncryption && model.display && links && links.length > 0
|
active: !currentRoom.usesEncryption && model.display && links && links.length > 0 && currentRoom.urlPreviewEnabled
|
||||||
visible: Config.showLinkPreview && active
|
visible: Config.showLinkPreview && active
|
||||||
sourceComponent: linkPreviewer.loaded ? linkPreviewComponent : loadingComponent
|
sourceComponent: linkPreviewer.loaded ? linkPreviewComponent : loadingComponent
|
||||||
|
|
||||||
|
|||||||
90
src/qml/Component/Timeline/LocationDelegate.qml
Normal file
90
src/qml/Component/Timeline/LocationDelegate.qml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtLocation 5.15
|
||||||
|
import QtPositioning 5.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
TimelineContainer {
|
||||||
|
id: locationDelegate
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.maximumWidth: locationDelegate.contentMaxWidth
|
||||||
|
Layout.preferredWidth: locationDelegate.contentMaxWidth
|
||||||
|
Map {
|
||||||
|
id: map
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: locationDelegate.contentMaxWidth / 16 * 9
|
||||||
|
|
||||||
|
center: QtPositioning.coordinate(model.latitude, model.longitude)
|
||||||
|
zoomLevel: 15
|
||||||
|
plugin: Plugin {
|
||||||
|
name: "osm"
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.useragent"
|
||||||
|
value: Application.name + "/" + Application.version + " (kde-devel@kde.org)"
|
||||||
|
}
|
||||||
|
PluginParameter {
|
||||||
|
name: "osm.mapping.providersrepository.address"
|
||||||
|
value: "https://autoconfig.kde.org/qtlocation/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCopyrightLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
|
||||||
|
|
||||||
|
MapQuickItem {
|
||||||
|
id: point
|
||||||
|
|
||||||
|
anchorPoint.x: sourceItem.width / 2
|
||||||
|
anchorPoint.y: sourceItem.height
|
||||||
|
coordinate: QtPositioning.coordinate(model.latitude, model.longitude)
|
||||||
|
autoFadeIn: false
|
||||||
|
|
||||||
|
sourceItem: Kirigami.Icon {
|
||||||
|
width: height
|
||||||
|
height: Kirigami.Units.iconSizes.huge
|
||||||
|
source: "gps"
|
||||||
|
isMask: true
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.verticalCenterOffset: -parent.height / 8
|
||||||
|
visible: model.asset === "m.pin"
|
||||||
|
width: height
|
||||||
|
height: parent.height / 3 + 1
|
||||||
|
source: "pin"
|
||||||
|
isMask: true
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
}
|
||||||
|
Kirigami.Avatar {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.verticalCenterOffset: -parent.height / 8
|
||||||
|
visible: model.asset === "m.self"
|
||||||
|
width: height
|
||||||
|
height: parent.height / 3 + 1
|
||||||
|
name: model.author.name ?? model.author.displayName
|
||||||
|
source: model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
|
||||||
|
color: model.author.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openMessageContext(model, "", model.message)
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openMessageContext(model, "", model.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ import org.kde.neochat 1.0
|
|||||||
TimelineContainer {
|
TimelineContainer {
|
||||||
id: messageDelegate
|
id: messageDelegate
|
||||||
|
|
||||||
property bool isEmote: false
|
|
||||||
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
|
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
|
||||||
|
|
||||||
innerObject: ColumnLayout {
|
innerObject: ColumnLayout {
|
||||||
@@ -22,7 +21,6 @@ TimelineContainer {
|
|||||||
id: label
|
id: label
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: currentRoom.chatBoxEditId !== model.eventId
|
visible: currentRoom.chatBoxEditId !== model.eventId
|
||||||
isEmote: messageDelegate.isEmote
|
|
||||||
}
|
}
|
||||||
Loader {
|
Loader {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@@ -119,6 +119,8 @@ QQC2.TextArea {
|
|||||||
selectionStart: root.selectionStart
|
selectionStart: root.selectionStart
|
||||||
selectionEnd: root.selectionEnd
|
selectionEnd: root.selectionEnd
|
||||||
room: root.room // We don't care about saving for edits so this is OK.
|
room: root.room // We don't care about saving for edits so this is OK.
|
||||||
|
mentionColor: Kirigami.Theme.linkColor
|
||||||
|
errorColor: Kirigami.Theme.negativeTextColor
|
||||||
}
|
}
|
||||||
|
|
||||||
TextMetrics {
|
TextMetrics {
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ Item {
|
|||||||
RichLabel {
|
RichLabel {
|
||||||
textMessage: reply.display
|
textMessage: reply.display
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
isReplyLabel: true
|
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
enabled: !hoveredLink
|
enabled: !hoveredLink
|
||||||
|
|||||||
@@ -14,27 +14,7 @@ TextEdit {
|
|||||||
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u20D0-\u2fff]|[\u3190-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
|
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u20D0-\u2fff]|[\u3190-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
|
||||||
readonly property var hasSpoiler: /data-mx-spoiler/g
|
readonly property var hasSpoiler: /data-mx-spoiler/g
|
||||||
|
|
||||||
property bool isEmote: false
|
property string textMessage: model.display
|
||||||
property bool isReplyLabel: false
|
|
||||||
|
|
||||||
readonly property var linkRegex: /(href=["'])?(\b(https?):\/\/[^\s\<\>\"\'\\\?\:\)\(]+(\(.*?\))*(\?(?=[a-z])[^\s\\\)]+|$)?)/g
|
|
||||||
property string textMessage: model.display.includes("http")
|
|
||||||
? model.display.replace(linkRegex, function() {
|
|
||||||
if (arguments[0].includes("/_matrix/media/r0/download/")) {
|
|
||||||
return arguments[0];
|
|
||||||
}
|
|
||||||
if (arguments[1]) {
|
|
||||||
return arguments[0];
|
|
||||||
}
|
|
||||||
const l = arguments[2];
|
|
||||||
if ([".", ","].includes(l[l.length-1])) {
|
|
||||||
const link = l.substring(0, l.length-1);
|
|
||||||
const leftover = l[l.length-1];
|
|
||||||
return `<a href="${link}">${link}</a>${leftover}`;
|
|
||||||
}
|
|
||||||
return `<a href="${l}">${l}</a>`;
|
|
||||||
})
|
|
||||||
: model.display
|
|
||||||
property bool spoilerRevealed: !hasSpoiler.test(textMessage)
|
property bool spoilerRevealed: !hasSpoiler.test(textMessage)
|
||||||
|
|
||||||
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
|
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
|
||||||
@@ -53,6 +33,9 @@ table {
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
code {
|
||||||
|
background-color:" + Kirigami.Theme.alternateBackgroundColor + ";
|
||||||
|
}
|
||||||
table th,
|
table th,
|
||||||
table td {
|
table td {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
@@ -75,7 +58,7 @@ a{
|
|||||||
background: " + Kirigami.Theme.textColor + ";
|
background: " + Kirigami.Theme.textColor + ";
|
||||||
}
|
}
|
||||||
" : "") + "
|
" : "") + "
|
||||||
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited && !contentLabel.isReplyLabel ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
|
</style>" + textMessage
|
||||||
|
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||||
|
|||||||
@@ -42,6 +42,6 @@ QQC2.ItemDelegate {
|
|||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Config.blur ? "transparent" : Kirigami.Theme.backgroundColor
|
color: Config.blur ? "transparent" : Kirigami.Theme.backgroundColor
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
Kirigami.Theme.colorSet: Config.compactLayout ? Kirigami.Theme.View : Kirigami.Theme.Window
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,5 +134,12 @@ QQC2.Control {
|
|||||||
folded = !folded
|
folded = !folded
|
||||||
foldedChanged()
|
foldedChanged()
|
||||||
}
|
}
|
||||||
|
AvatarFlow {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
visible: showReadMarkers
|
||||||
|
model: readMarkers
|
||||||
|
toolTipText: readMarkersString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ ColumnLayout {
|
|||||||
default property alias innerObject : column.children
|
default property alias innerObject : column.children
|
||||||
|
|
||||||
property Item hoverComponent: hoverActions ?? null
|
property Item hoverComponent: hoverActions ?? null
|
||||||
property bool isEmote: false
|
|
||||||
property bool cardBackground: true
|
property bool cardBackground: true
|
||||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout
|
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout
|
||||||
property bool isHighlighted: model.isHighlighted || isTemporaryHighlighted
|
property bool isHighlighted: model.isHighlighted || isTemporaryHighlighted
|
||||||
@@ -212,7 +211,7 @@ ColumnLayout {
|
|||||||
id: rowLayout
|
id: rowLayout
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
visible: model.showAuthor && !isEmote
|
visible: model.showAuthor
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: nameLabel
|
id: nameLabel
|
||||||
@@ -306,6 +305,7 @@ ColumnLayout {
|
|||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
|
enabled: isTemporaryHighlighted
|
||||||
ColorAnimation {target: bubbleBackground; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.InOutCubic}
|
ColorAnimation {target: bubbleBackground; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.InOutCubic}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,14 @@ ColumnLayout {
|
|||||||
Layout.leftMargin: showUserMessageOnRight ? 0 : bubble.x + bubble.anchors.leftMargin
|
Layout.leftMargin: showUserMessageOnRight ? 0 : bubble.x + bubble.anchors.leftMargin
|
||||||
Layout.rightMargin: showUserMessageOnRight ? Kirigami.Units.largeSpacing : 0
|
Layout.rightMargin: showUserMessageOnRight ? Kirigami.Units.largeSpacing : 0
|
||||||
|
|
||||||
visible: eventType !== MessageEventModel.State && eventType !== MessageEventModel.Notice && reaction != undefined && reaction.length > 0
|
visible: delegateType !== MessageEventModel.State && delegateType !== MessageEventModel.Notice && reaction != undefined && reaction.length > 0
|
||||||
|
}
|
||||||
|
AvatarFlow {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
visible: showReadMarkers
|
||||||
|
model: readMarkers
|
||||||
|
toolTipText: readMarkersString
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVisibleInTimeline() {
|
function isVisibleInTimeline() {
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ QQC2.Dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x: Math.round((parent.width - width) / 2)
|
x: parent ? Math.round((parent.width - width) / 2) : 0
|
||||||
y: Math.round((parent.height - height) / 2)
|
y: parent ? Math.round((parent.height - height) / 2) : 0
|
||||||
modal: true
|
modal: true
|
||||||
|
|
||||||
footer: QQC2.DialogButtonBox {
|
footer: QQC2.DialogButtonBox {
|
||||||
@@ -35,7 +35,7 @@ QQC2.Dialog {
|
|||||||
text: i18n("Sign out")
|
text: i18n("Sign out")
|
||||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Controller.logout(Controller.activeConnection, true);
|
Controller.activeConnection.logout();
|
||||||
root.close();
|
root.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,13 +18,13 @@ Kirigami.OverlaySheet {
|
|||||||
contentItem: Kirigami.FormLayout {
|
contentItem: Kirigami.FormLayout {
|
||||||
QQC2.TextField {
|
QQC2.TextField {
|
||||||
id: roomNameField
|
id: roomNameField
|
||||||
Kirigami.FormData.label: i18n("Room Name")
|
Kirigami.FormData.label: i18n("Room name:")
|
||||||
onAccepted: roomTopicField.forceActiveFocus();
|
onAccepted: roomTopicField.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.TextField {
|
QQC2.TextField {
|
||||||
id: roomTopicField
|
id: roomTopicField
|
||||||
Kirigami.FormData.label: i18n("Room Topic")
|
Kirigami.FormData.label: i18n("Room topic:")
|
||||||
onAccepted: okButton.forceActiveFocus();
|
onAccepted: okButton.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ Labs.MenuBar {
|
|||||||
|
|
||||||
Labs.MenuItem {
|
Labs.MenuItem {
|
||||||
text: i18nc("menu", "New Private Chat…")
|
text: i18nc("menu", "New Private Chat…")
|
||||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.activeConnection
|
||||||
onTriggered: pushReplaceLayer("qrc:/StartChatPage.qml", {connection: Controller.activeConnection})
|
onTriggered: pushReplaceLayer("qrc:/StartChatPage.qml", {connection: Controller.activeConnection})
|
||||||
}
|
}
|
||||||
Labs.MenuItem {
|
Labs.MenuItem {
|
||||||
text: i18nc("menu", "New Group…")
|
text: i18nc("menu", "New Group…")
|
||||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.activeConnection
|
||||||
shortcut: StandardKey.New
|
shortcut: StandardKey.New
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
const dialog = createRoomDialog.createObject(root.overlay)
|
const dialog = createRoomDialog.createObject(root.overlay)
|
||||||
|
|||||||
45
src/qml/Page/RoomList/AccountMenu.qml
Normal file
45
src/qml/Page/RoomList/AccountMenu.qml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.19 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import '../Dialog' as Dialog
|
||||||
|
|
||||||
|
QQC2.Menu {
|
||||||
|
id: root
|
||||||
|
margins: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: i18n("Edit this account")
|
||||||
|
icon.name: "document-edit"
|
||||||
|
onTriggered: pageStack.pushDialogLayer("qrc:/AccountEditorPage.qml", {
|
||||||
|
connection: Controller.activeConnection
|
||||||
|
}, {
|
||||||
|
title: i18n("Account editor")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: i18n("Notification settings")
|
||||||
|
icon.name: "notifications"
|
||||||
|
onTriggered: pageStack.pushDialogLayer("qrc:/SettingsPage.qml", {defaultPage: "notifications"}, { title: i18n("Configure")})
|
||||||
|
}
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: i18n("Devices")
|
||||||
|
icon.name: "computer-symbolic"
|
||||||
|
onTriggered: pageStack.pushDialogLayer("qrc:/SettingsPage.qml", {defaultPage: "devices"}, { title: i18n("Configure")})
|
||||||
|
}
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: i18n("Logout")
|
||||||
|
icon.name: "list-remove-user"
|
||||||
|
onTriggered: confirmLogoutDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog.ConfirmLogout {
|
||||||
|
id: confirmLogoutDialog
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/qml/Page/RoomList/CollapsedRoomDelegate.qml
Normal file
53
src/qml/Page/RoomList/CollapsedRoomDelegate.qml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQml.Models 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
import org.kde.kitemmodels 1.0
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
import './' as RoomList
|
||||||
|
|
||||||
|
QQC2.ItemDelegate {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var currentRoom
|
||||||
|
required property bool categoryVisible
|
||||||
|
required property string filterText
|
||||||
|
required property string avatar
|
||||||
|
required property string name
|
||||||
|
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
width: ListView.view.width
|
||||||
|
height: visible ? ListView.view.width : 0
|
||||||
|
|
||||||
|
visible: root.categoryVisible || filterText.length > 0 || Config.mergeRoomList
|
||||||
|
|
||||||
|
contentItem: Kirigami.Avatar {
|
||||||
|
source: root.avatar ? `image://mxc/${root.avatar}` : ""
|
||||||
|
name: root.name || i18n("No Name")
|
||||||
|
|
||||||
|
sourceSize {
|
||||||
|
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: RoomManager.enterRoom(root.currentRoom)
|
||||||
|
|
||||||
|
Keys.onEnterPressed: RoomManager.enterRoom(root.currentRoom)
|
||||||
|
Keys.onReturnPressed: RoomManager.enterRoom(root.currentRoom)
|
||||||
|
|
||||||
|
QQC2.ToolTip.visible: text.length > 0 && hovered
|
||||||
|
QQC2.ToolTip.text: root.name ?? ""
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user