Compare commits
1 Commits
work/test-
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49e4fd1b00 |
@@ -2,5 +2,7 @@
|
|||||||
; SPDX-License-Identifier: CC0-1.0
|
; SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
[BlueprintSettings]
|
[BlueprintSettings]
|
||||||
|
kde/frameworks/extra-cmake-modules.version=master
|
||||||
|
kde/unreleased/kirigami-addons.version=master
|
||||||
kde/applications/neochat.packageAppx=True
|
kde/applications/neochat.packageAppx=True
|
||||||
libs/qt.qtMajorVersion=6
|
libs/qt.qtMajorVersion=6
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"id": "org.kde.neochat",
|
"id": "org.kde.neochat",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"runtime": "org.kde.Platform",
|
"runtime": "org.kde.Platform",
|
||||||
"runtime-version": "6.9",
|
"runtime-version": "6.8",
|
||||||
"sdk": "org.kde.Sdk",
|
"sdk": "org.kde.Sdk",
|
||||||
"command": "neochat",
|
"command": "neochat",
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -149,6 +149,27 @@
|
|||||||
],
|
],
|
||||||
"builddir": true
|
"builddir": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "qcoro",
|
||||||
|
"buildsystem": "cmake-ninja",
|
||||||
|
"config-opts": [
|
||||||
|
"-DQCORO_BUILD_EXAMPLES=OFF",
|
||||||
|
"-DBUILD_TESTING=OFF"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.11.0.tar.gz",
|
||||||
|
"sha256": "9942c5b4c533192f6c5954dc6d10178b3829075e6a621b67df73f0a4b74d8297",
|
||||||
|
"x-checker-data": {
|
||||||
|
"type": "anitya",
|
||||||
|
"project-id": 236236,
|
||||||
|
"stable-only": true,
|
||||||
|
"url-template": "https://github.com/danvratil/qcoro/archive/refs/tags/v$version.tar.gz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "kunifiedpush",
|
"name": "kunifiedpush",
|
||||||
"buildsystem": "cmake-ninja",
|
"buildsystem": "cmake-ninja",
|
||||||
|
|||||||
@@ -3,20 +3,19 @@
|
|||||||
|
|
||||||
include:
|
include:
|
||||||
- project: sysadmin/ci-utilities
|
- project: sysadmin/ci-utilities
|
||||||
ref: work/switch-vm-ci
|
|
||||||
file:
|
file:
|
||||||
#- /gitlab-templates/reuse-lint.yml
|
- /gitlab-templates/reuse-lint.yml
|
||||||
#- /gitlab-templates/json-validation.yml
|
- /gitlab-templates/json-validation.yml
|
||||||
#- /gitlab-templates/xml-lint.yml
|
- /gitlab-templates/xml-lint.yml
|
||||||
#- /gitlab-templates/yaml-lint.yml
|
- /gitlab-templates/yaml-lint.yml
|
||||||
#- /gitlab-templates/android-qt6.yml
|
- /gitlab-templates/android-qt6.yml
|
||||||
- /gitlab-templates/linux-qt6.yml
|
- /gitlab-templates/linux-qt6.yml
|
||||||
- /gitlab-templates/linux-qt6-next.yml
|
- /gitlab-templates/linux-qt6-next.yml
|
||||||
#- /gitlab-templates/windows-qt6.yml
|
- /gitlab-templates/windows-qt6.yml
|
||||||
#- /gitlab-templates/freebsd-qt6.yml
|
- /gitlab-templates/freebsd-qt6.yml
|
||||||
- /gitlab-templates/flatpak.yml
|
- /gitlab-templates/flatpak.yml
|
||||||
- /gitlab-templates/snap-snapcraft-lxd.yml
|
- /gitlab-templates/snap-snapcraft-lxd.yml
|
||||||
#- /gitlab-templates/craft-android-qt6-apks.yml
|
- /gitlab-templates/craft-android-qt6-apks.yml
|
||||||
#- /gitlab-templates/craft-appimage-qt6.yml
|
- /gitlab-templates/craft-appimage-qt6.yml
|
||||||
- /gitlab-templates/craft-windows-x86-64-qt6.yml
|
- /gitlab-templates/craft-windows-x86-64-qt6.yml
|
||||||
- /gitlab-templates/craft-windows-appx-qt6.yml
|
- /gitlab-templates/craft-windows-appx-qt6.yml
|
||||||
|
|||||||
@@ -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 "25")
|
set(RELEASE_SERVICE_VERSION_MAJOR "25")
|
||||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
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}")
|
||||||
|
|
||||||
@@ -164,7 +164,6 @@ endif()
|
|||||||
|
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
find_package(Sqlite3)
|
find_package(Sqlite3)
|
||||||
set(BUILD_TESTING FALSE)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
ki18n_install(po)
|
ki18n_install(po)
|
||||||
@@ -179,7 +178,7 @@ add_definitions(-DQT_NO_FOREACH)
|
|||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
||||||
if (BUILD_TESTING)
|
if (BUILD_TESTING)
|
||||||
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test HttpServer)
|
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
|
||||||
add_subdirectory(autotests)
|
add_subdirectory(autotests)
|
||||||
# add_subdirectory(appiumtests)
|
# add_subdirectory(appiumtests)
|
||||||
if (NOT ANDROID)
|
if (NOT ANDROID)
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
add_library(neochat_server STATIC server.cpp)
|
|
||||||
|
|
||||||
target_link_libraries(neochat_server PUBLIC Qt::HttpServer QuotientQt6)
|
|
||||||
|
|
||||||
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
|
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
|
||||||
|
|
||||||
ecm_add_test(
|
ecm_add_test(
|
||||||
@@ -89,6 +85,6 @@ ecm_add_test(
|
|||||||
|
|
||||||
ecm_add_test(
|
ecm_add_test(
|
||||||
actionstest.cpp
|
actionstest.cpp
|
||||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
LINK_LIBRARIES neochat Qt::Test
|
||||||
TEST_NAME actionstest
|
TEST_NAME actionstest
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,11 +6,9 @@
|
|||||||
#include <QSignalSpy>
|
#include <QSignalSpy>
|
||||||
#include <QVariantList>
|
#include <QVariantList>
|
||||||
|
|
||||||
#include "accountmanager.h"
|
|
||||||
#include "chatbarcache.h"
|
#include "chatbarcache.h"
|
||||||
#include "models/actionsmodel.h"
|
#include "models/actionsmodel.h"
|
||||||
|
|
||||||
#include "server.h"
|
|
||||||
#include "testutils.h"
|
#include "testutils.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -23,12 +21,10 @@ class ActionsTest : public QObject
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Connection *connection = nullptr;
|
Connection *connection = nullptr;
|
||||||
NeoChatRoom *room = nullptr;
|
TestUtils::TestRoom *room = nullptr;
|
||||||
|
|
||||||
void expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message);
|
void expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message);
|
||||||
|
|
||||||
Server server;
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void initTestCase();
|
void initTestCase();
|
||||||
void testActions();
|
void testActions();
|
||||||
@@ -38,23 +34,8 @@ private Q_SLOTS:
|
|||||||
|
|
||||||
void ActionsTest::initTestCase()
|
void ActionsTest::initTestCase()
|
||||||
{
|
{
|
||||||
Connection::setRoomType<NeoChatRoom>();
|
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||||
server.start();
|
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-min-sync.json"));
|
||||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
|
||||||
auto accountManager = new AccountManager(true);
|
|
||||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
|
||||||
connection = accountManager->accounts()->front();
|
|
||||||
auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
|
||||||
server.inviteUser(roomId, u"@invited:example.com"_s);
|
|
||||||
server.banUser(roomId, u"@banned:example.com"_s);
|
|
||||||
server.joinUser(roomId, u"@example:example.com"_s);
|
|
||||||
|
|
||||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
|
||||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
|
||||||
QVERIFY(room);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActionsTest::testActions_data()
|
void ActionsTest::testActions_data()
|
||||||
@@ -109,7 +90,7 @@ static ActionsModel::Action findAction(const QString &name)
|
|||||||
void ActionsTest::expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message)
|
void ActionsTest::expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message)
|
||||||
{
|
{
|
||||||
auto action = findAction(actionName);
|
auto action = findAction(actionName);
|
||||||
QSignalSpy spy(room, &NeoChatRoom::showMessage);
|
QSignalSpy spy(room, &TestUtils::TestRoom::showMessage);
|
||||||
auto result = action.handle(args, room, nullptr);
|
auto result = action.handle(args, room, nullptr);
|
||||||
auto expected = QVariantList {type, message};
|
auto expected = QVariantList {type, message};
|
||||||
auto signal = spy.takeFirst();
|
auto signal = spy.takeFirst();
|
||||||
@@ -125,26 +106,14 @@ void ActionsTest::testInvite()
|
|||||||
QCOMPARE(room->memberState(u"@banned:example.com"_s), Membership::Ban);
|
QCOMPARE(room->memberState(u"@banned:example.com"_s), Membership::Ban);
|
||||||
expectMessage(u"invite"_s, connection->userId(), MessageType::Positive, u"You are already in this room."_s);
|
expectMessage(u"invite"_s, connection->userId(), MessageType::Positive, u"You are already in this room."_s);
|
||||||
QCOMPARE(room->memberState(connection->userId()), Membership::Join);
|
QCOMPARE(room->memberState(connection->userId()), Membership::Join);
|
||||||
expectMessage(u"invite"_s, u"@example:example.com"_s, MessageType::Information, u"@example:example.com is already in this room."_s);
|
expectMessage(u"invite"_s, u"@example:example.org"_s, MessageType::Information, u"@example:example.org is already in this room."_s);
|
||||||
QCOMPARE(room->memberState(u"@example:example.com"_s), Membership::Join);
|
QCOMPARE(room->memberState(u"@example:example.org"_s), Membership::Join);
|
||||||
|
|
||||||
QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Leave);
|
QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Leave);
|
||||||
expectMessage(u"invite"_s, u"@user:example.com"_s, MessageType::Positive, u"@user:example.com was invited into this room."_s);
|
expectMessage(u"invite"_s, u"@user:example.com"_s, MessageType::Positive, u"@user:example.com was invited into this room."_s);
|
||||||
|
|
||||||
QSignalSpy spy(room, &NeoChatRoom::changed);
|
//TODO mock server, wait for invite state to change
|
||||||
QVERIFY(spy.wait());
|
//TODO QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Invite);
|
||||||
|
|
||||||
auto tries = 0;
|
|
||||||
|
|
||||||
while (room->memberState(u"@user:example.com"_s) != Membership::Invite) {
|
|
||||||
QVERIFY(spy.wait());
|
|
||||||
tries += 1;
|
|
||||||
if (tries > 3) {
|
|
||||||
QVERIFY(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Invite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QTEST_MAIN(ActionsTest)
|
QTEST_MAIN(ActionsTest)
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDNTCCAh2gAwIBAgIUXbyWfTfcvVLrVB1qx36pW/7IkwMwDQYJKoZIhvcNAQEL
|
|
||||||
BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
|
|
||||||
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDEyMjQxNTAxMDNaGA8yNTcyMDcy
|
|
||||||
NDE1MDEwM1owQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
|
|
||||||
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD
|
|
||||||
ggEPADCCAQoCggEBAKlxZ540TQ1uUDAR7ZJ9ue0PzcD2dPmblIIddyekvZS59V7X
|
|
||||||
drhamclXpHE2EelR87Sexst0BaHH/jmrHwxCtwbeXHZ8ueJHkGHJ5DLZCCiwfG+Q
|
|
||||||
gml7wlSXxXz37vie2tdlZh2yJSM8yvLAYceHb2zOskaGvul7ZITIS0JrPc3o6VZk
|
|
||||||
+MYGkYtA2JfUsv3jH4oQbxOf7RXqhWNAXbB+3hlwRBwMIdyoBNK6YS9QSrTeS9jj
|
|
||||||
UqgO5QmaQZOVvpaPf1Y/rHHLd2Qa6+a/cCJ1sr2biagb75AihpQFsK/oy6D1PP70
|
|
||||||
zTe7hPWn/efEpmtCV7CQ8ti4cRu0Kjy0T8grtCsCAwEAAaMhMB8wHQYDVR0OBBYE
|
|
||||||
FIFlylzwADNLfgTDNkhFeFelaEDxMA0GCSqGSIb3DQEBCwUAA4IBAQBQ2rw4GLIU
|
|
||||||
v+GY7Qru9LttkrQPd2bZXKxDMd/jT+wjmMVtqS4MAsCuDYwaYLjU1aWyqy0mN+lY
|
|
||||||
A17kD0VjBNBy45sYqkZveY0ks8mCScBemtrIDmjz2tiueecBIEASwEPBOZgv5/MV
|
|
||||||
cz864FiChF+2r8Zl8bhycGy9DEpRjzYKvIQWSDHQ3zpuh3iBnjfoieLHWX2kKCpk
|
|
||||||
ouS3V6485rHNCWsZT5IcCwfBFQkOuWRJpIazpz4AfwZh1TK9+bgiKA5EyZjSNrKw
|
|
||||||
xGQSpMSTRQMB0/FOCL/AixhN9unVFUViqUcdtSfoHE1VyBHv9kDT/cYms/Xl4B0t
|
|
||||||
/ZSQJ/D/Km1+
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCpcWeeNE0NblAw
|
|
||||||
Ee2SfbntD83A9nT5m5SCHXcnpL2UufVe13a4WpnJV6RxNhHpUfO0nsbLdAWhx/45
|
|
||||||
qx8MQrcG3lx2fLniR5BhyeQy2QgosHxvkIJpe8JUl8V89+74ntrXZWYdsiUjPMry
|
|
||||||
wGHHh29szrJGhr7pe2SEyEtCaz3N6OlWZPjGBpGLQNiX1LL94x+KEG8Tn+0V6oVj
|
|
||||||
QF2wft4ZcEQcDCHcqATSumEvUEq03kvY41KoDuUJmkGTlb6Wj39WP6xxy3dkGuvm
|
|
||||||
v3AidbK9m4moG++QIoaUBbCv6Mug9Tz+9M03u4T1p/3nxKZrQlewkPLYuHEbtCo8
|
|
||||||
tE/IK7QrAgMBAAECggEAH9qmeKrra2F4KLlOGNKS//qPGz4Z+ozhi95/NpA1Zb7Z
|
|
||||||
3pUSCBFcROo5i2D3WA4kiymoRLpQjrv60puVcCggoWVvK4VCKsR6Y6/hOx/q9T9M
|
|
||||||
fWrE4ZC3FVEc+uPfZJT0nja9TkrdyXSV0LITD8Ap1eI7yJ9vR5R/bqj64QcpLMrU
|
|
||||||
QeoQIy1oTMR+qdjj33duyRwBZU3Yf8FRB2iW6OILZ8hzFo1jngec7dph9a1RK4e0
|
|
||||||
mEPdc9ywsKlDM7P0Y7zdmjar5XtQn87GiwNhz23f1fzCC2axLtOW0Xm4e4Qumehb
|
|
||||||
WrIi6Vfq8IWMglU7QrBJ7iR0Ls+XoKA5GxomV2IJZQKBgQDoIkOl5YGPQ3iGR+WK
|
|
||||||
e5/2Ml4G/uURzYiOlzSsyfoPXyO4EI2BJd5HkH+EvfgRx4xKkxUZRJdzR7llYPl8
|
|
||||||
BFYcFitvhO8SbD0mNAB5YW7f+3v1pgEN2umzoKd389Zx5WqTZ7YB1VG5RN/Q1JJL
|
|
||||||
2JM0Xgamq2vNtx3roRPxDBeW7QKBgQC63R/bmACJbgIzfaVBX4Zie3NQG0/Hf+gF
|
|
||||||
LnBwUmQDZOR7MY+kSiIUVMn3NuZRiCSCFBVwApruyK8r535JCibTVm5PWjvhFddY
|
|
||||||
LgaPOCKGlm9TLScjoH1pErYgG3uJ4nXeRfXhg4mco6EkrC7RzQywrd0VDoqpuc1Y
|
|
||||||
EKfEsYk8dwKBgE+mSh3nNOBKX1V73+f3aTiZqaeu2DyWkG+UtE9BclrJ40Cp9VPG
|
|
||||||
AZH+o7KRWEgJdzqzYv7riSfWCWgesRv7hOxYMwktzLY+i3DLUQpVAy05ZhwwnJX7
|
|
||||||
ckrfKfc/pGoqNLplUI8qecMfPciy14vMwR2r0Y5orTHFzi9mcqg35PQ1AoGAW2LX
|
|
||||||
OLq+0HdHhk0Va8I+450CSRQCUUvhed87SANTPEG0Z/dWC3/h6NWKrGdh/k+5oxAV
|
|
||||||
Z+EuSkdFPBCLt0bKtCKZ8h7sF+lplotz08kdQXsC2MfFU2wiySdIgK1QHp/tCxZl
|
|
||||||
6LM+sqdnoJrAjwRcB3AQJkMlV1ox7ba/hbdZqYMCgYBS6+JUXSSASpm5ZHd32a8m
|
|
||||||
xwryEZ7H6Hek6lvMHdxmwoKat5dCavxw64nrtyeeGZpg1W3zLLyamF9x/8kMyr6y
|
|
||||||
KKvtBfJ5sCvAbt80o9Pbs6R3yDB3AKiD3s3PQK7lol1nhE/8IbsF2r8JEQVcYd/k
|
|
||||||
oBzkl7MrMyLhhaCqSxwqQQ==
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "server.h"
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QHttpServer>
|
|
||||||
#include <QHttpServerResponder>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QNetworkReply>
|
|
||||||
#include <QSslCertificate>
|
|
||||||
#include <QSslKey>
|
|
||||||
#include <QSslServer>
|
|
||||||
#include <QUuid>
|
|
||||||
|
|
||||||
#include <Quotient/networkaccessmanager.h>
|
|
||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
|
||||||
|
|
||||||
QString generateEventId()
|
|
||||||
{
|
|
||||||
return u"$"_s + QString::fromLatin1(QCryptographicHash::hash(QUuid::createUuid().toString().toLatin1(), QCryptographicHash::Sha1).toBase64());
|
|
||||||
}
|
|
||||||
|
|
||||||
QString generateRoomId()
|
|
||||||
{
|
|
||||||
return u"!%1:localhost:1234"_s
|
|
||||||
.arg(QString::fromLatin1(QCryptographicHash::hash(QUuid::createUuid().toString().toLatin1(), QCryptographicHash::Sha1).toBase64()))
|
|
||||||
.replace(u'/', QChar());
|
|
||||||
}
|
|
||||||
|
|
||||||
Server::Server()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::start()
|
|
||||||
{
|
|
||||||
QObject::connect(Quotient::NetworkAccessManager::instance(),
|
|
||||||
&QNetworkAccessManager::sslErrors,
|
|
||||||
Quotient::NetworkAccessManager::instance(),
|
|
||||||
[](QNetworkReply *reply) {
|
|
||||||
reply->ignoreSslErrors();
|
|
||||||
});
|
|
||||||
m_server.route(u"/.well-known/matrix/client"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
|
|
||||||
responder.write(QJsonDocument(QJsonObject{
|
|
||||||
{u"m.homeserver"_s, QJsonObject{{u"base_url"_s, u"https://localhost:1234"_s}}},
|
|
||||||
}),
|
|
||||||
QHttpServerResponder::StatusCode::Ok);
|
|
||||||
});
|
|
||||||
m_server.route(u"/_matrix/client/versions"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
|
|
||||||
responder.write(QJsonDocument(QJsonObject{
|
|
||||||
{u"versions"_s,
|
|
||||||
QJsonArray{
|
|
||||||
u"v1.0"_s,
|
|
||||||
u"v1.1"_s,
|
|
||||||
u"v1.2"_s,
|
|
||||||
u"v1.3"_s,
|
|
||||||
u"v1.4"_s,
|
|
||||||
u"v1.5"_s,
|
|
||||||
u"v1.6"_s,
|
|
||||||
u"v1.7"_s,
|
|
||||||
u"v1.8"_s,
|
|
||||||
u"v1.9"_s,
|
|
||||||
u"v1.10"_s,
|
|
||||||
u"v1.11"_s,
|
|
||||||
u"v1.12"_s,
|
|
||||||
u"v1.13"_s,
|
|
||||||
}},
|
|
||||||
}),
|
|
||||||
QHttpServerResponder::StatusCode::Ok);
|
|
||||||
});
|
|
||||||
m_server.route(u"/_matrix/client/v3/capabilities"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
|
|
||||||
responder.write(
|
|
||||||
QJsonDocument(QJsonObject{{u"capabilities"_s,
|
|
||||||
QJsonObject{
|
|
||||||
{u"m.room_versions"_s, QJsonObject{{u"m.available"_s, QJsonObject{{u"1"_s, u"stable"_s}}}, {u"default"_s, u"1"_s}}},
|
|
||||||
}}}),
|
|
||||||
QHttpServerResponder::StatusCode::Ok);
|
|
||||||
});
|
|
||||||
m_server.route(u"/_matrix/client/v3/account/whoami"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
|
|
||||||
responder.write(QJsonDocument(QJsonObject{
|
|
||||||
{u"device_id"_s, u"device_id_1234"_s},
|
|
||||||
{u"user_id"_s, u"@user:localhost:1234"_s},
|
|
||||||
}),
|
|
||||||
QHttpServerResponder::StatusCode::Ok);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_server.route(u"/_matrix/client/v3/login"_s, QHttpServerRequest::Method::Post, [](QHttpServerResponder &responder) {
|
|
||||||
// TODO
|
|
||||||
// if data["identifier"]["user"] != "user" or data["password"] != "1234":
|
|
||||||
// abort(403)
|
|
||||||
responder.write(QJsonDocument(QJsonObject{
|
|
||||||
{u"access_token"_s, u"token_login"_s},
|
|
||||||
{u"device_id"_s, u"device_1234"_s},
|
|
||||||
{u"user_id"_s, u"@user:localhost:1234"_s},
|
|
||||||
}),
|
|
||||||
QHttpServerResponder::StatusCode::Ok);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_server.route(u"/_matrix/client/v3/login"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
|
|
||||||
responder.write(QJsonDocument(QJsonObject{
|
|
||||||
{u"flows"_s, QJsonArray{QJsonObject{{u"type"_s, u"m.login.password"_s}}}},
|
|
||||||
}),
|
|
||||||
QHttpServerResponder::StatusCode::Ok);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_server.route(u"/_matrix/client/v3/rooms/<arg>/invite"_s,
|
|
||||||
QHttpServerRequest::Method::Post,
|
|
||||||
[this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) {
|
|
||||||
m_invitedUsers[roomId] += QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString();
|
|
||||||
responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) {
|
|
||||||
QMap<QString, QJsonArray> stateEvents;
|
|
||||||
|
|
||||||
for (const auto &[roomId, matrixId] : m_roomsToCreate) {
|
|
||||||
stateEvents[roomId] += QJsonObject{
|
|
||||||
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
|
|
||||||
{u"event_id"_s, generateEventId()},
|
|
||||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
|
||||||
{u"room_id"_s, roomId},
|
|
||||||
{u"sender"_s, matrixId},
|
|
||||||
{u"state_key"_s, QString()},
|
|
||||||
{u"type"_s, u"m.room.create"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
stateEvents[roomId] += QJsonObject{
|
|
||||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
|
||||||
{u"event_id"_s, generateEventId()},
|
|
||||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
|
||||||
{u"room_id"_s, roomId},
|
|
||||||
{u"sender"_s, matrixId},
|
|
||||||
{u"state_key"_s, matrixId},
|
|
||||||
{u"type"_s, u"m.room.member"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
m_roomsToCreate.clear();
|
|
||||||
for (const auto &roomId : m_invitedUsers.keys()) {
|
|
||||||
const auto &values = m_invitedUsers[roomId];
|
|
||||||
for (const auto &value : values) {
|
|
||||||
stateEvents[roomId] += QJsonObject{
|
|
||||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
|
|
||||||
{u"event_id"_s, generateEventId()},
|
|
||||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
|
||||||
{u"room_id"_s, roomId},
|
|
||||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
|
||||||
{u"state_key"_s, value},
|
|
||||||
{u"type"_s, u"m.room.member"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_invitedUsers.clear();
|
|
||||||
|
|
||||||
for (const auto &roomId : m_bannedUsers.keys()) {
|
|
||||||
const auto &values = m_bannedUsers[roomId];
|
|
||||||
for (const auto &value : values) {
|
|
||||||
stateEvents[roomId] += QJsonObject{
|
|
||||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
|
|
||||||
{u"event_id"_s, generateEventId()},
|
|
||||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
|
||||||
{u"room_id"_s, roomId},
|
|
||||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
|
||||||
{u"state_key"_s, value},
|
|
||||||
{u"type"_s, u"m.room.member"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_bannedUsers.clear();
|
|
||||||
|
|
||||||
for (const auto &roomId : m_joinedUsers.keys()) {
|
|
||||||
const auto &values = m_joinedUsers[roomId];
|
|
||||||
for (const auto &value : values) {
|
|
||||||
stateEvents[roomId] += QJsonObject{
|
|
||||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
|
||||||
{u"event_id"_s, generateEventId()},
|
|
||||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
|
||||||
{u"room_id"_s, roomId},
|
|
||||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
|
||||||
{u"state_key"_s, value},
|
|
||||||
{u"type"_s, u"m.room.member"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_joinedUsers.clear();
|
|
||||||
|
|
||||||
QJsonObject rooms;
|
|
||||||
for (const auto &roomId : stateEvents.keys()) {
|
|
||||||
rooms[roomId] = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}}};
|
|
||||||
}
|
|
||||||
|
|
||||||
responder.write(QJsonDocument(QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}}), QHttpServerResponder::StatusCode::Ok);
|
|
||||||
});
|
|
||||||
|
|
||||||
QSslConfiguration config;
|
|
||||||
QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s);
|
|
||||||
key.open(QFile::ReadOnly);
|
|
||||||
config.setPrivateKey(QSslKey(&key, QSsl::Rsa));
|
|
||||||
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front());
|
|
||||||
m_sslServer.setSslConfiguration(config);
|
|
||||||
if (!m_sslServer.listen(QHostAddress::LocalHost, 1234) || !m_server.bind(&m_sslServer)) {
|
|
||||||
qFatal() << "Server failed to listen on a port.";
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
qWarning() << "Server listening";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Server::createRoom(const QString &matrixId)
|
|
||||||
{
|
|
||||||
auto roomId = generateRoomId();
|
|
||||||
m_roomsToCreate += {roomId, matrixId};
|
|
||||||
return roomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::inviteUser(const QString &roomId, const QString &matrixId)
|
|
||||||
{
|
|
||||||
m_invitedUsers[roomId] += matrixId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::banUser(const QString &roomId, const QString &matrixId)
|
|
||||||
{
|
|
||||||
m_bannedUsers[roomId] += matrixId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::joinUser(const QString &roomId, const QString &matrixId)
|
|
||||||
{
|
|
||||||
m_joinedUsers[roomId] += matrixId;
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
#include <QHttpServer>
|
|
||||||
#include <QSslServer>
|
|
||||||
|
|
||||||
class Server
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Server();
|
|
||||||
|
|
||||||
void start();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a room and place the user with id matrixId in it.
|
|
||||||
* Returns the room's id
|
|
||||||
*/
|
|
||||||
QString createRoom(const QString &matrixId);
|
|
||||||
|
|
||||||
void inviteUser(const QString &roomId, const QString &matrixId);
|
|
||||||
void banUser(const QString &roomId, const QString &matrixId);
|
|
||||||
void joinUser(const QString &roomId, const QString &matrixId);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QHttpServer m_server;
|
|
||||||
QSslServer m_sslServer;
|
|
||||||
|
|
||||||
QHash<QString, QList<QString>> m_invitedUsers;
|
|
||||||
QHash<QString, QList<QString>> m_bannedUsers;
|
|
||||||
QHash<QString, QList<QString>> m_joinedUsers;
|
|
||||||
|
|
||||||
QList<std::pair<QString, QString>> m_roomsToCreate;
|
|
||||||
};
|
|
||||||
@@ -61,7 +61,6 @@ private Q_SLOTS:
|
|||||||
void receiveRichStrikethrough();
|
void receiveRichStrikethrough();
|
||||||
void receiveRichtextIn();
|
void receiveRichtextIn();
|
||||||
void receiveRichMxcUrl();
|
void receiveRichMxcUrl();
|
||||||
void receiveRichPlainUrl_data();
|
|
||||||
void receiveRichPlainUrl();
|
void receiveRichPlainUrl();
|
||||||
void receiveRichEdited_data();
|
void receiveRichEdited_data();
|
||||||
void receiveRichEdited();
|
void receiveRichEdited();
|
||||||
@@ -265,8 +264,6 @@ void TextHandlerTest::sendCustomTags_data()
|
|||||||
QTest::newRow("inside code block spoiler") << u"```||apple||```"_s << u"<code>||apple||</code>"_s;
|
QTest::newRow("inside code block spoiler") << u"```||apple||```"_s << u"<code>||apple||</code>"_s;
|
||||||
QTest::newRow("outside code block spoiler") << u"||apple|| ```||banana||``` ||pear||"_s
|
QTest::newRow("outside code block spoiler") << u"||apple|| ```||banana||``` ||pear||"_s
|
||||||
<< u"<span data-mx-spoiler>apple</span> <code>||banana||</code> <span data-mx-spoiler>pear</span>"_s;
|
<< u"<span data-mx-spoiler>apple</span> <code>||banana||</code> <span data-mx-spoiler>pear</span>"_s;
|
||||||
QTest::newRow("complex spoiler") << u"Between `formFactor == Horizontal||Vertical` and `location == top||left||bottom||right`"_s
|
|
||||||
<< u"Between <code>formFactor == Horizontal||Vertical</code> and <code>location == top||left||bottom||right</code>"_s;
|
|
||||||
|
|
||||||
// strikethrough
|
// strikethrough
|
||||||
QTest::newRow("incomplete strikethrough") << u"~~test"_s << u"~~test"_s;
|
QTest::newRow("incomplete strikethrough") << u"~~test"_s << u"~~test"_s;
|
||||||
@@ -451,32 +448,6 @@ void TextHandlerTest::receiveRichMxcUrl()
|
|||||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextHandlerTest::receiveRichPlainUrl_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QString>("input");
|
|
||||||
QTest::addColumn<QString>("output");
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
QTest::addRow("link 1")
|
|
||||||
<< u"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>"_s
|
|
||||||
<< u"<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>"_s;
|
|
||||||
|
|
||||||
// Another real case. The linkification wasn't handling it when a single link
|
|
||||||
// contains what looks like and email. It was broken into 3 but needs to
|
|
||||||
// be just single link.
|
|
||||||
QTest::addRow("link 2")
|
|
||||||
<< u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s
|
|
||||||
<< u"<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>"_s;
|
|
||||||
|
|
||||||
QTest::addRow("email") << uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
|
|
||||||
<< uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
|
|
||||||
QTest::addRow("mxid")
|
|
||||||
<< u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s
|
|
||||||
<< u"<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>"_s;
|
|
||||||
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s << u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For when your rich input string has a plain text url left in.
|
* For when your rich input string has a plain text url left in.
|
||||||
*
|
*
|
||||||
@@ -485,13 +456,46 @@ void TextHandlerTest::receiveRichPlainUrl_data()
|
|||||||
*/
|
*/
|
||||||
void TextHandlerTest::receiveRichPlainUrl()
|
void TextHandlerTest::receiveRichPlainUrl()
|
||||||
{
|
{
|
||||||
QFETCH(QString, input);
|
// This is an actual link that caused trouble which is why it's so long. Keeping
|
||||||
QFETCH(QString, output);
|
// so we can confirm consistent behaviour for complex urls.
|
||||||
|
const QString testInputStringLink1 =
|
||||||
|
u"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>"_s;
|
||||||
|
const QString testOutputStringLink1 =
|
||||||
|
u"<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>"_s;
|
||||||
|
|
||||||
|
// 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 = u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s;
|
||||||
|
const QString testOutputStringLink2 =
|
||||||
|
u"<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>"_s;
|
||||||
|
|
||||||
|
QString testInputStringEmail = uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s;
|
||||||
|
QString testOutputStringEmail = uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
|
||||||
|
|
||||||
|
QString testInputStringMxId = u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s;
|
||||||
|
QString testOutputStringMxId =
|
||||||
|
u"<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>"_s;
|
||||||
|
|
||||||
|
QString testInputStringMxIdWithPrefix = u"a @user:kde.org b"_s;
|
||||||
|
QString testOutputStringMxIdWithPrefix = u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
|
||||||
|
|
||||||
TextHandler testTextHandler;
|
TextHandler testTextHandler;
|
||||||
testTextHandler.setData(input);
|
testTextHandler.setData(testInputStringLink1);
|
||||||
|
|
||||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), output);
|
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);
|
||||||
|
|
||||||
|
testTextHandler.setData(testInputStringMxIdWithPrefix);
|
||||||
|
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxIdWithPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextHandlerTest::receiveRichEdited_data()
|
void TextHandlerTest::receiveRichEdited_data()
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ void TimelineMessageModelTest::switchEmptyRoom()
|
|||||||
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
|
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
|
||||||
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s);
|
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s);
|
||||||
|
|
||||||
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *)));
|
QSignalSpy spy(model, SIGNAL(roomChanged()));
|
||||||
|
|
||||||
QCOMPARE(model->room(), nullptr);
|
QCOMPARE(model->room(), nullptr);
|
||||||
model->setRoom(firstRoom);
|
model->setRoom(firstRoom);
|
||||||
@@ -77,7 +77,7 @@ void TimelineMessageModelTest::switchSyncedRoom()
|
|||||||
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
|
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
|
||||||
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
|
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
|
||||||
|
|
||||||
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *)));
|
QSignalSpy spy(model, SIGNAL(roomChanged()));
|
||||||
|
|
||||||
QCOMPARE(model->room(), nullptr);
|
QCOMPARE(model->room(), nullptr);
|
||||||
model->setRoom(firstRoom);
|
model->setRoom(firstRoom);
|
||||||
@@ -208,7 +208,7 @@ void TimelineMessageModelTest::idToRow()
|
|||||||
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
|
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
|
||||||
model->setRoom(room);
|
model->setRoom(room);
|
||||||
|
|
||||||
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0);
|
QCOMPARE(model->eventIdToRow(u"$153456789:example.org"_s), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineMessageModelTest::cleanup()
|
void TimelineMessageModelTest::cleanup()
|
||||||
|
|||||||
@@ -477,8 +477,6 @@
|
|||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="25.04.3" date="2025-07-03"/>
|
|
||||||
<release version="25.04.2" date="2025-06-05"/>
|
|
||||||
<release version="25.04.1" date="2025-05-08"/>
|
<release version="25.04.1" date="2025-05-08"/>
|
||||||
<release version="25.04.0" date="2025-04-17"/>
|
<release version="25.04.0" date="2025-04-17"/>
|
||||||
<release version="24.12.3" date="2025-03-06"/>
|
<release version="24.12.3" date="2025-03-06"/>
|
||||||
|
|||||||
1558
po/ar/neochat.po
1558
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1372
po/ast/neochat.po
1372
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
1482
po/az/neochat.po
1482
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1469
po/ca/neochat.po
1469
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1437
po/cs/neochat.po
1437
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1451
po/da/neochat.po
1451
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1458
po/de/neochat.po
1458
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1465
po/el/neochat.po
1465
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1465
po/en_GB/neochat.po
1465
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1450
po/eo/neochat.po
1450
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1405
po/es/neochat.po
1405
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1582
po/eu/neochat.po
1582
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1618
po/fi/neochat.po
1618
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1662
po/fr/neochat.po
1662
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1669
po/gl/neochat.po
1669
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
1418
po/he/neochat.po
1418
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
1455
po/hi/neochat.po
1455
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
1944
po/hu/neochat.po
1944
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1552
po/ia/neochat.po
1552
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1472
po/id/neochat.po
1472
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1456
po/ie/neochat.po
1456
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1541
po/it/neochat.po
1541
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1364
po/ja/neochat.po
1364
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1416
po/ka/neochat.po
1416
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1579
po/ko/neochat.po
1579
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1382
po/lt/neochat.po
1382
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1696
po/lv/neochat.po
1696
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1415
po/nl/neochat.po
1415
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1495
po/nn/neochat.po
1495
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1480
po/pa/neochat.po
1480
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1476
po/pl/neochat.po
1476
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1468
po/pt/neochat.po
1468
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1472
po/pt_BR/neochat.po
1472
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1476
po/ru/neochat.po
1476
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1451
po/sa/neochat.po
1451
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
1467
po/sk/neochat.po
1467
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1421
po/sl/neochat.po
1421
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1599
po/sv/neochat.po
1599
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1463
po/ta/neochat.po
1463
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1463
po/tok/neochat.po
1463
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1559
po/tr/neochat.po
1559
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1429
po/uk/neochat.po
1429
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1373
po/zh_CN/neochat.po
1373
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1389
po/zh_TW/neochat.po
1389
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,6 @@ add_subdirectory(libneochat)
|
|||||||
add_subdirectory(login)
|
add_subdirectory(login)
|
||||||
add_subdirectory(rooms)
|
add_subdirectory(rooms)
|
||||||
add_subdirectory(roominfo)
|
add_subdirectory(roominfo)
|
||||||
add_subdirectory(messagecontent)
|
|
||||||
add_subdirectory(timeline)
|
add_subdirectory(timeline)
|
||||||
add_subdirectory(spaces)
|
add_subdirectory(spaces)
|
||||||
add_subdirectory(chatbar)
|
add_subdirectory(chatbar)
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ add_library(neochat STATIC
|
|||||||
windowcontroller.h
|
windowcontroller.h
|
||||||
models/serverlistmodel.cpp
|
models/serverlistmodel.cpp
|
||||||
models/serverlistmodel.h
|
models/serverlistmodel.h
|
||||||
|
logger.cpp
|
||||||
|
logger.h
|
||||||
models/notificationsmodel.cpp
|
models/notificationsmodel.cpp
|
||||||
models/notificationsmodel.h
|
models/notificationsmodel.h
|
||||||
proxycontroller.cpp
|
proxycontroller.cpp
|
||||||
@@ -45,9 +47,6 @@ if(ANDROID OR WIN32)
|
|||||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||||
QT_QML_SOURCE_TYPENAME ShareAction
|
QT_QML_SOURCE_TYPENAME ShareAction
|
||||||
)
|
)
|
||||||
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
|
|
||||||
QT_QML_SOURCE_TYPENAME GlobalMenu
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||||
@@ -59,7 +58,9 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
|||||||
qml/RoomPage.qml
|
qml/RoomPage.qml
|
||||||
qml/ManualRoomDialog.qml
|
qml/ManualRoomDialog.qml
|
||||||
qml/ExplorerDelegate.qml
|
qml/ExplorerDelegate.qml
|
||||||
|
qml/ImageEditorPage.qml
|
||||||
qml/NeochatMaximizeComponent.qml
|
qml/NeochatMaximizeComponent.qml
|
||||||
|
qml/TypingPane.qml
|
||||||
qml/QuickSwitcher.qml
|
qml/QuickSwitcher.qml
|
||||||
qml/AttachmentPane.qml
|
qml/AttachmentPane.qml
|
||||||
qml/QuickFormatBar.qml
|
qml/QuickFormatBar.qml
|
||||||
@@ -108,7 +109,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
|||||||
org.kde.neochat.libneochat
|
org.kde.neochat.libneochat
|
||||||
org.kde.neochat.rooms
|
org.kde.neochat.rooms
|
||||||
org.kde.neochat.roominfo
|
org.kde.neochat.roominfo
|
||||||
org.kde.neochat.messagecontent
|
|
||||||
org.kde.neochat.timeline
|
org.kde.neochat.timeline
|
||||||
org.kde.neochat.spaces
|
org.kde.neochat.spaces
|
||||||
org.kde.neochat.settings
|
org.kde.neochat.settings
|
||||||
@@ -124,10 +124,7 @@ if(NOT ANDROID AND NOT WIN32)
|
|||||||
qml/EditMenu.qml
|
qml/EditMenu.qml
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
qt_target_qml_sources(neochat QML_FILES
|
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
|
||||||
qml/ShareActionStub.qml
|
|
||||||
qml/GlobalMenuStub.qml
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
@@ -177,7 +174,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
|
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
|
||||||
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
|
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
|
||||||
target_link_libraries(neochat PUBLIC
|
target_link_libraries(neochat PUBLIC
|
||||||
LibNeoChat
|
LibNeoChat
|
||||||
Timeline
|
Timeline
|
||||||
@@ -202,7 +199,6 @@ target_link_libraries(neochat PUBLIC
|
|||||||
QuotientQt6
|
QuotientQt6
|
||||||
Login
|
Login
|
||||||
Rooms
|
Rooms
|
||||||
MessageContent
|
|
||||||
Spaces
|
Spaces
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -357,10 +353,3 @@ install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
|||||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||||
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
|
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
|
||||||
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.neochat")
|
|
||||||
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "NeoChat")
|
|
||||||
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${RELEASE_SERVICE_VERSION})
|
|
||||||
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${RELEASE_SERVICE_VERSION})
|
|
||||||
endif ()
|
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ Controller::Controller(QObject *parent)
|
|||||||
MessageModel::setHiddenFilter(hiddenEventFilter);
|
MessageModel::setHiddenFilter(hiddenEventFilter);
|
||||||
RoomListModel::setHiddenFilter(hiddenEventFilter);
|
RoomListModel::setHiddenFilter(hiddenEventFilter);
|
||||||
RoomTreeModel::setHiddenFilter(hiddenEventFilter);
|
RoomTreeModel::setHiddenFilter(hiddenEventFilter);
|
||||||
NeoChatRoom::setHiddenFilter(hiddenEventFilter);
|
|
||||||
|
|
||||||
MediaSizeHelper::setMaxSize(NeoChatConfig::mediaMaxWidth(), NeoChatConfig::mediaMaxHeight());
|
MediaSizeHelper::setMaxSize(NeoChatConfig::mediaMaxWidth(), NeoChatConfig::mediaMaxHeight());
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::MediaMaxWidthChanged, this, []() {
|
connect(NeoChatConfig::self(), &NeoChatConfig::MediaMaxWidthChanged, this, []() {
|
||||||
@@ -407,9 +406,4 @@ void Controller::markImageShown(const QString &eventId)
|
|||||||
m_shownImages.append(eventId);
|
m_shownImages.append(eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::markImageHidden(const QString &eventId)
|
|
||||||
{
|
|
||||||
m_shownImages.removeAll(eventId);
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "moc_controller.cpp"
|
#include "moc_controller.cpp"
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE bool isImageShown(const QString &eventId);
|
Q_INVOKABLE bool isImageShown(const QString &eventId);
|
||||||
Q_INVOKABLE void markImageShown(const QString &eventId);
|
Q_INVOKABLE void markImageShown(const QString &eventId);
|
||||||
Q_INVOKABLE void markImageHidden(const QString &eventId);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit Controller(QObject *parent = nullptr);
|
explicit Controller(QObject *parent = nullptr);
|
||||||
|
|||||||
223
src/app/logger.cpp
Normal file
223
src/app/logger.cpp
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
// SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
|
||||||
|
// SPDX-FileCopyrightText: 2002 Holger Freyther <freyther@kde.org>
|
||||||
|
// SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
using namespace Qt::StringLiterals;
|
||||||
|
|
||||||
|
static QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr;
|
||||||
|
static QtMessageHandler oldHandler = nullptr;
|
||||||
|
static bool e2eeDebugEnabled = false;
|
||||||
|
|
||||||
|
class FileDebugStream : public QIODevice
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
FileDebugStream()
|
||||||
|
: mType(QtCriticalMsg)
|
||||||
|
{
|
||||||
|
open(WriteOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSequential() const override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
qint64 readData(char *, qint64) override
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
qint64 readLineData(char *, qint64) override
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 writeData(const char *data, qint64 len) override
|
||||||
|
{
|
||||||
|
if (!mFileName.isEmpty()) {
|
||||||
|
QFile outputFile(mFileName);
|
||||||
|
outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered);
|
||||||
|
outputFile.write(data, len);
|
||||||
|
outputFile.putChar('\n');
|
||||||
|
outputFile.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFileName(const QString &fileName)
|
||||||
|
{
|
||||||
|
mFileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setType(QtMsgType type)
|
||||||
|
{
|
||||||
|
mType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString mFileName;
|
||||||
|
QtMsgType mType;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DebugPrivate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DebugPrivate()
|
||||||
|
: origHandler(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~DebugPrivate()
|
||||||
|
{
|
||||||
|
qInstallMessageHandler(origHandler);
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void log(QtMsgType type, const QMessageLogContext &context, const QString &message)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&mutex);
|
||||||
|
QByteArray buf;
|
||||||
|
QTextStream str(&buf);
|
||||||
|
str << QDateTime::currentDateTime().toString(Qt::ISODate) << u" ["_s;
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg:
|
||||||
|
str << u"DEBUG"_s;
|
||||||
|
break;
|
||||||
|
case QtInfoMsg:
|
||||||
|
str << u"INFO "_s;
|
||||||
|
break;
|
||||||
|
case QtWarningMsg:
|
||||||
|
str << u"WARN "_s;
|
||||||
|
break;
|
||||||
|
case QtFatalMsg:
|
||||||
|
str << u"FATAL"_s;
|
||||||
|
break;
|
||||||
|
case QtCriticalMsg:
|
||||||
|
str << u"CRITICAL"_s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
str << u"] "_s << context.category << u": "_s;
|
||||||
|
if (context.file && *context.file && context.line) {
|
||||||
|
str << context.file << u":"_s << context.line << u": "_s;
|
||||||
|
}
|
||||||
|
if (context.function && *context.function) {
|
||||||
|
str << context.function << u": "_s;
|
||||||
|
}
|
||||||
|
str << message << u"\n"_s;
|
||||||
|
str.flush();
|
||||||
|
file.write(buf.constData(), buf.size());
|
||||||
|
file.flush();
|
||||||
|
|
||||||
|
if (oldHandler && (!context.category || (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled))) {
|
||||||
|
oldHandler(type, context, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setName(const QString &appName)
|
||||||
|
{
|
||||||
|
name = appName;
|
||||||
|
|
||||||
|
if (file.isOpen()) {
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
const auto &filePath = u"%1%2%3"_s.arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), QDir::separator(), appName);
|
||||||
|
|
||||||
|
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator());
|
||||||
|
auto entryList = dir.entryList({appName + u".*"_s});
|
||||||
|
std::sort(entryList.begin(), entryList.end(), [](const auto &left, const auto &right) {
|
||||||
|
auto leftIndex = left.split(u"."_s).last().toInt();
|
||||||
|
auto rightIndex = right.split(u"."_s).last().toInt();
|
||||||
|
return leftIndex > rightIndex;
|
||||||
|
});
|
||||||
|
for (const auto &entry : entryList) {
|
||||||
|
bool ok = false;
|
||||||
|
const auto index = entry.split(u"."_s).last().toInt(&ok);
|
||||||
|
if (!ok) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QFileInfo info(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + entry);
|
||||||
|
if (info.exists()) {
|
||||||
|
QFile file(info.absoluteFilePath());
|
||||||
|
if (index > 50) {
|
||||||
|
file.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto &newName = u"%1.%2"_s.arg(filePath, QString::number(index + 1));
|
||||||
|
const auto success = file.copy(newName);
|
||||||
|
if (success) {
|
||||||
|
file.remove();
|
||||||
|
} else {
|
||||||
|
qFatal("Cannot rename log file '%s' to '%s': %s",
|
||||||
|
qUtf8Printable(file.fileName()),
|
||||||
|
qUtf8Printable(newName),
|
||||||
|
qUtf8Printable(file.errorString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo finfo(filePath);
|
||||||
|
if (!finfo.absoluteDir().exists()) {
|
||||||
|
QDir().mkpath(finfo.absolutePath());
|
||||||
|
}
|
||||||
|
file.setFileName(filePath + u".0"_s);
|
||||||
|
file.open(QIODevice::WriteOnly | QIODevice::Unbuffered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOrigHandler(QtMessageHandler origHandler_)
|
||||||
|
{
|
||||||
|
origHandler = origHandler_;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMutex mutex;
|
||||||
|
QFile file;
|
||||||
|
QString name;
|
||||||
|
QtMessageHandler origHandler;
|
||||||
|
QByteArray loggingCategory;
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_GLOBAL_STATIC(DebugPrivate, sInstance)
|
||||||
|
|
||||||
|
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg:
|
||||||
|
case QtInfoMsg:
|
||||||
|
case QtWarningMsg:
|
||||||
|
case QtCriticalMsg:
|
||||||
|
sInstance()->log(type, context, message);
|
||||||
|
break;
|
||||||
|
case QtFatalMsg:
|
||||||
|
sInstance()->log(QtInfoMsg, context, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void filter(QLoggingCategory *category)
|
||||||
|
{
|
||||||
|
if (qstrcmp(category->categoryName(), "quotient.e2ee") == 0) {
|
||||||
|
category->setEnabled(QtDebugMsg, true);
|
||||||
|
} else if (oldCategoryFilter) {
|
||||||
|
oldCategoryFilter(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initLogging()
|
||||||
|
{
|
||||||
|
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtInfoMsg).isEnabled(QtDebugMsg);
|
||||||
|
oldCategoryFilter = QLoggingCategory::installFilter(filter);
|
||||||
|
oldHandler = qInstallMessageHandler(messageHandler);
|
||||||
|
sInstance->setOrigHandler(oldHandler);
|
||||||
|
sInstance->setName(u"neochat.log"_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "logger.moc"
|
||||||
9
src/app/logger.h
Normal file
9
src/app/logger.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initlalize logging to file and enables some additional categories, which will only be logged to the file
|
||||||
|
*/
|
||||||
|
void initLogging();
|
||||||
@@ -49,6 +49,7 @@
|
|||||||
#include "blurhashimageprovider.h"
|
#include "blurhashimageprovider.h"
|
||||||
#include "colorschemer.h"
|
#include "colorschemer.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
#include "logger.h"
|
||||||
#include "login.h"
|
#include "login.h"
|
||||||
#include "registration.h"
|
#include "registration.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
@@ -137,11 +138,6 @@ int main(int argc, char *argv[])
|
|||||||
font.setHintingPreference(QFont::PreferNoHinting);
|
font.setHintingPreference(QFont::PreferNoHinting);
|
||||||
app.setFont(font);
|
app.setFont(font);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
QApplication::setStyle(u"breeze"_s);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||||
|
|
||||||
QGuiApplication::setOrganizationName("KDE"_L1);
|
QGuiApplication::setOrganizationName("KDE"_L1);
|
||||||
@@ -181,6 +177,8 @@ int main(int argc, char *argv[])
|
|||||||
KCrash::initialize();
|
KCrash::initialize();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
initLogging();
|
||||||
|
|
||||||
Connection::setEncryptionDefault(true);
|
Connection::setEncryptionDefault(true);
|
||||||
Connection::setDirectChatEncryptionDefault(true);
|
Connection::setDirectChatEncryptionDefault(true);
|
||||||
|
|
||||||
@@ -197,9 +195,6 @@ int main(int argc, char *argv[])
|
|||||||
parser.addPositionalArgument(u"urls"_s, i18n("Supports matrix: url scheme"));
|
parser.addPositionalArgument(u"urls"_s, i18n("Supports matrix: url scheme"));
|
||||||
parser.addOption(QCommandLineOption("ignore-ssl-errors"_L1, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
|
parser.addOption(QCommandLineOption("ignore-ssl-errors"_L1, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
|
||||||
|
|
||||||
QCommandLineOption replaceOption({QStringLiteral("replace")}, i18nc("command line description", "Replace an existing instance"));
|
|
||||||
parser.addOption(replaceOption);
|
|
||||||
|
|
||||||
QCommandLineOption testOption("test"_L1, i18n("Only used for autotests"));
|
QCommandLineOption testOption("test"_L1, i18n("Only used for autotests"));
|
||||||
testOption.setFlags(QCommandLineOption::HiddenFromHelp);
|
testOption.setFlags(QCommandLineOption::HiddenFromHelp);
|
||||||
parser.addOption(testOption);
|
parser.addOption(testOption);
|
||||||
@@ -236,7 +231,7 @@ int main(int argc, char *argv[])
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_KDBUSADDONS
|
#ifdef HAVE_KDBUSADDONS
|
||||||
KDBusService service(KDBusService::Unique | (parser.isSet(replaceOption) ? KDBusService::Replace : KDBusService::StartupOption(0)));
|
KDBusService service(KDBusService::Unique);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto accountManager = std::make_unique<AccountManager>(parser.isSet("test"_L1));
|
const auto accountManager = std::make_unique<AccountManager>(parser.isSet("test"_L1));
|
||||||
@@ -244,6 +239,13 @@ int main(int argc, char *argv[])
|
|||||||
LoginHelper::instance().setAccountManager(accountManager.get());
|
LoginHelper::instance().setAccountManager(accountManager.get());
|
||||||
Registration::instance().setAccountManager(accountManager.get());
|
Registration::instance().setAccountManager(accountManager.get());
|
||||||
|
|
||||||
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_settingsPlugin)
|
||||||
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_roomsPlugin)
|
||||||
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
|
||||||
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
|
||||||
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_loginPlugin)
|
||||||
|
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
|
||||||
|
|
||||||
qml_register_types_org_kde_neochat();
|
qml_register_types_org_kde_neochat();
|
||||||
qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s);
|
qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s);
|
||||||
|
|
||||||
|
|||||||
@@ -78,12 +78,6 @@
|
|||||||
<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="MarkReadCondition" type="Enum">
|
|
||||||
<label>The sort order for the rooms in the list.</label>
|
|
||||||
<choices name="::TimelineMarkReadCondition::Condition">
|
|
||||||
</choices>
|
|
||||||
<default>2</default>
|
|
||||||
</entry>
|
|
||||||
<entry name="ShowStateEvent" type="bool">
|
<entry name="ShowStateEvent" type="bool">
|
||||||
<label>Show state events in the timeline</label>
|
<label>Show state events in the timeline</label>
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
@@ -211,10 +205,6 @@
|
|||||||
<label>Enable add phone numbers as 3PIDs</label>
|
<label>Enable add phone numbers as 3PIDs</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="Calls" type="bool">
|
|
||||||
<label>Enable audio and video calling</label>
|
|
||||||
<default>false</default>
|
|
||||||
</entry>
|
|
||||||
</group>
|
</group>
|
||||||
<group name="Security">
|
<group name="Security">
|
||||||
<entry name="RejectUnknownInvites" type="bool">
|
<entry name="RejectUnknownInvites" type="bool">
|
||||||
|
|||||||
@@ -36,18 +36,14 @@ KirigamiComponents.ConvergentContextMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18nc("@action:inmenu", "Switch Account")
|
|
||||||
icon.name: "system-switch-user"
|
|
||||||
shortcut: "Ctrl+U"
|
|
||||||
onTriggered: accountSwitchDialog.createObject(QQC2.Overlay.overlay, {
|
|
||||||
connection: root.connection
|
|
||||||
}).open();
|
|
||||||
}
|
|
||||||
QQC2.Action {
|
QQC2.Action {
|
||||||
text: i18n("Edit This Account")
|
text: i18n("Edit This Account")
|
||||||
icon.name: "document-edit"
|
icon.name: "document-edit"
|
||||||
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection});
|
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), {
|
||||||
|
connection: root.connection
|
||||||
|
}, {
|
||||||
|
title: i18n("Account editor")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Action {
|
QQC2.Action {
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ Delegates.RoundedItemDelegate {
|
|||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
acceptedDevices: PointerDevice.TouchScreen
|
acceptedDevices: PointerDevice.TouchScreen
|
||||||
onTapped: root.selected()
|
|
||||||
onLongPressed: root.contextMenuRequested()
|
onLongPressed: root.contextMenuRequested()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
import org.kde.neochat
|
|
||||||
|
|
||||||
Item {
|
|
||||||
required property NeoChatConnection connection
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,7 @@ ColumnLayout {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property NeoChatRoom currentRoom
|
required property NeoChatRoom currentRoom
|
||||||
readonly property var invitingMember: currentRoom.qmlSafeMember(currentRoom.invitingUserId)
|
readonly property var invitingMember: currentRoom.member(currentRoom.invitingUserId)
|
||||||
readonly property string inviteTimestamp: root.currentRoom.inviteTimestamp.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
readonly property string inviteTimestamp: root.currentRoom.inviteTimestamp.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
@@ -33,7 +33,7 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
name: root.invitingMember.displayName
|
name: root.invitingMember.displayName
|
||||||
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
|
source: root.invitingMember.avatarUrl
|
||||||
color: root.invitingMember.color
|
color: root.invitingMember.color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ Kirigami.ApplicationWindow {
|
|||||||
showExisting: true
|
showExisting: true
|
||||||
onConnectionChosen: root.load()
|
onConnectionChosen: root.load()
|
||||||
}
|
}
|
||||||
columnView.columnResizeMode: pageStack.wideMode ? Kirigami.ColumnView.DynamicColumns : Kirigami.ColumnView.SingleColumn
|
|
||||||
globalToolBar.canContainHandles: true
|
globalToolBar.canContainHandles: true
|
||||||
globalToolBar {
|
globalToolBar {
|
||||||
style: Kirigami.ApplicationHeaderStyle.ToolBar
|
style: Kirigami.ApplicationHeaderStyle.ToolBar
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ Components.AlbumMaximizeComponent {
|
|||||||
id: saveAsDialog
|
id: saveAsDialog
|
||||||
Dialogs.FileDialog {
|
Dialogs.FileDialog {
|
||||||
fileMode: Dialogs.FileDialog.SaveFile
|
fileMode: Dialogs.FileDialog.SaveFile
|
||||||
currentFolder: NeoChatConfig.lastSaveDirectory.length > 0 ? NeoChatConfig.lastSaveDirectory : Core.StandardPaths.writableLocation(Core.StandardPaths.DownloadLocation)
|
currentFolder: root.saveFolder
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
NeoChatConfig.lastSaveDirectory = currentFolder;
|
NeoChatConfig.lastSaveDirectory = currentFolder;
|
||||||
NeoChatConfig.save();
|
NeoChatConfig.save();
|
||||||
|
|||||||
@@ -28,14 +28,6 @@ Kirigami.Page {
|
|||||||
placeholderText: root.placeholder
|
placeholderText: root.placeholder
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
wrapMode: TextEdit.Wrap
|
wrapMode: TextEdit.Wrap
|
||||||
focus: true
|
|
||||||
|
|
||||||
Keys.onReturnPressed: event => {
|
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
|
||||||
root.accepted(reason.text);
|
|
||||||
root.closeDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Kirigami.Theme.backgroundColor
|
color: Kirigami.Theme.backgroundColor
|
||||||
@@ -58,7 +50,6 @@ Kirigami.Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
icon.name: "dialog-cancel-symbolic"
|
|
||||||
text: i18nc("@action", "Cancel")
|
text: i18nc("@action", "Cancel")
|
||||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
|
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
|
||||||
onClicked: root.closeDialog()
|
onClicked: root.closeDialog()
|
||||||
|
|||||||
@@ -11,14 +11,15 @@ import org.kde.kirigami as Kirigami
|
|||||||
import org.kde.kitemmodels
|
import org.kde.kitemmodels
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
import org.kde.neochat.chatbar
|
||||||
|
|
||||||
Kirigami.Page {
|
Kirigami.Page {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
/**
|
/// Not readonly because of the separate window view.
|
||||||
* @brief The NeoChatRoom the delegate is being displayed in.
|
property NeoChatRoom currentRoom: RoomManager.currentRoom
|
||||||
*/
|
|
||||||
readonly property NeoChatRoom currentRoom: RoomManager.currentRoom
|
required property NeoChatConnection connection
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The TimelineModel to use.
|
* @brief The TimelineModel to use.
|
||||||
@@ -58,6 +59,11 @@ Kirigami.Page {
|
|||||||
*/
|
*/
|
||||||
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
|
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
|
||||||
|
|
||||||
|
property bool loading: !root.currentRoom || (root.currentRoom.timelineSize === 0 && !root.currentRoom.allHistoryLoaded)
|
||||||
|
|
||||||
|
/// Disable cancel shortcut. Used by the separate window since it provides its own cancel implementation.
|
||||||
|
property bool disableCancelShortcut: false
|
||||||
|
|
||||||
title: root.currentRoom ? root.currentRoom.displayName : ""
|
title: root.currentRoom ? root.currentRoom.displayName : ""
|
||||||
focus: true
|
focus: true
|
||||||
padding: 0
|
padding: 0
|
||||||
@@ -80,9 +86,9 @@ Kirigami.Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.currentRoom.connection
|
target: root.connection
|
||||||
function onIsOnlineChanged() {
|
function onIsOnlineChanged() {
|
||||||
if (!root.currentRoom.connection.isOnline) {
|
if (!root.connection.isOnline) {
|
||||||
banner.text = i18n("NeoChat is offline. Please check your network connection.");
|
banner.text = i18n("NeoChat is offline. Please check your network connection.");
|
||||||
banner.visible = true;
|
banner.visible = true;
|
||||||
banner.type = Kirigami.MessageType.Error;
|
banner.type = Kirigami.MessageType.Error;
|
||||||
@@ -103,15 +109,18 @@ Kirigami.Page {
|
|||||||
Loader {
|
Loader {
|
||||||
id: timelineViewLoader
|
id: timelineViewLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: root.currentRoom && !root.currentRoom.isInvite && !root.currentRoom.isSpace
|
active: root.currentRoom && !root.currentRoom.isInvite && !root.loading && !root.currentRoom.isSpace
|
||||||
// We need the loader to be active but invisible while the room is loading messages so signals in TimelineView work.
|
|
||||||
visible: !root.loading
|
|
||||||
sourceComponent: TimelineView {
|
sourceComponent: TimelineView {
|
||||||
id: timelineView
|
id: timelineView
|
||||||
|
currentRoom: root.currentRoom
|
||||||
|
page: root
|
||||||
|
timelineModel: root.timelineModel
|
||||||
messageFilterModel: root.messageFilterModel
|
messageFilterModel: root.messageFilterModel
|
||||||
compactLayout: NeoChatConfig.compactLayout
|
onFocusChatBar: {
|
||||||
fileDropEnabled: !Controller.isFlatpak
|
if (chatBarLoader.item) {
|
||||||
markReadCondition: NeoChatConfig.markReadCondition
|
chatBarLoader.item.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +152,14 @@ Kirigami.Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: root.loading && !invitationLoader.active && RoomManager.currentRoom && !spaceLoader.active
|
||||||
|
anchors.centerIn: parent
|
||||||
|
sourceComponent: Kirigami.LoadingPlaceholder {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
@@ -157,7 +174,12 @@ Kirigami.Page {
|
|||||||
id: chatBar
|
id: chatBar
|
||||||
width: parent.width
|
width: parent.width
|
||||||
currentRoom: root.currentRoom
|
currentRoom: root.currentRoom
|
||||||
connection: root.currentRoom.connection
|
connection: root.connection
|
||||||
|
onMessageSent: {
|
||||||
|
if (!timelineViewLoader.item.atYEnd) {
|
||||||
|
timelineViewLoader.item.goToLastMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,8 +196,21 @@ Kirigami.Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: StandardKey.Cancel
|
||||||
|
onActivated: {
|
||||||
|
if (!timelineViewLoader.item.atYEnd || !root.currentRoom.partiallyReadStats.empty()) {
|
||||||
|
timelineViewLoader.item.goToLastMessage();
|
||||||
|
root.currentRoom.markAllMessagesAsRead();
|
||||||
|
} else {
|
||||||
|
applicationWindow().pageStack.get(0).forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enabled: !root.disableCancelShortcut
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.currentRoom.connection
|
target: root.connection
|
||||||
function onJoinedRoom(room, invited) {
|
function onJoinedRoom(room, invited) {
|
||||||
if (root.currentRoom.id === invited.id) {
|
if (root.currentRoom.id === invited.id) {
|
||||||
RoomManager.resolveResource(room.id);
|
RoomManager.resolveResource(room.id);
|
||||||
@@ -231,7 +266,6 @@ Kirigami.Page {
|
|||||||
plainText: plainText,
|
plainText: plainText,
|
||||||
mimeType: mimeType,
|
mimeType: mimeType,
|
||||||
progressInfo: progressInfo,
|
progressInfo: progressInfo,
|
||||||
messageComponentType: messageComponentType,
|
|
||||||
});
|
});
|
||||||
contextMenu.popup();
|
contextMenu.popup();
|
||||||
}
|
}
|
||||||
@@ -261,7 +295,7 @@ Kirigami.Page {
|
|||||||
id: messageDelegateContextMenu
|
id: messageDelegateContextMenu
|
||||||
MessageDelegateContextMenu {
|
MessageDelegateContextMenu {
|
||||||
room: root.currentRoom
|
room: root.currentRoom
|
||||||
connection: root.currentRoom.connection
|
connection: root.connection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +303,7 @@ Kirigami.Page {
|
|||||||
id: fileDelegateContextMenu
|
id: fileDelegateContextMenu
|
||||||
FileDelegateContextMenu {
|
FileDelegateContextMenu {
|
||||||
room: root.currentRoom
|
room: root.currentRoom
|
||||||
connection: root.currentRoom.connection
|
connection: root.connection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ Kirigami.Dialog {
|
|||||||
text: root.user.id
|
text: root.user.id
|
||||||
elide: Qt.ElideRight
|
elide: Qt.ElideRight
|
||||||
elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
|
elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
|
||||||
|
onElideWidthChanged: console.warn(root.availableWidth, avatar.width, qrButton.width, elideWidth, elidedText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ RoomManager::RoomManager(QObject *parent)
|
|||||||
m_directChatsConfig = m_config->group(u"DirectChatsActive"_s);
|
m_directChatsConfig = m_config->group(u"DirectChatsActive"_s);
|
||||||
|
|
||||||
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
|
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
|
||||||
m_userListModel->setRoom(m_currentRoom);
|
|
||||||
m_timelineModel->setRoom(m_currentRoom);
|
m_timelineModel->setRoom(m_currentRoom);
|
||||||
m_sortFilterRoomTreeModel->setCurrentRoom(m_currentRoom);
|
m_sortFilterRoomTreeModel->setCurrentRoom(m_currentRoom);
|
||||||
|
m_userListModel->setRoom(m_currentRoom);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this](NeoChatConnection *connection) {
|
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this](NeoChatConnection *connection) {
|
||||||
@@ -96,7 +96,6 @@ RoomManager::RoomManager(QObject *parent)
|
|||||||
m_messageFilterModel->invalidate();
|
m_messageFilterModel->invalidate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_timelineModel->timelineMessageModel(), &MessageModel::modelResetComplete, this, &RoomManager::activateUserModel);
|
|
||||||
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());
|
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowAllEventsChanged, this, [this] {
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowAllEventsChanged, this, [this] {
|
||||||
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());
|
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());
|
||||||
@@ -304,12 +303,7 @@ void RoomManager::loadInitialRoom()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_isMobile) {
|
if (m_isMobile) {
|
||||||
QString lastSpace = m_lastSpaceConfig.readEntry(m_connection->userId(), QString());
|
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), false);
|
||||||
// We can't have empty keys in KConfig, so we stored it as "Home"
|
|
||||||
if (lastSpace == u"Home"_s) {
|
|
||||||
lastSpace.clear();
|
|
||||||
}
|
|
||||||
setCurrentSpace(lastSpace, false);
|
|
||||||
// We don't want to open a room on startup on mobile
|
// We don't want to open a room on startup on mobile
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -331,7 +325,14 @@ void RoomManager::openRoomForActiveConnection()
|
|||||||
setCurrentSpace({}, false);
|
setCurrentSpace({}, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), true);
|
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), false);
|
||||||
|
const auto &lastRoom = m_lastRoomConfig.readEntry(m_connection->userId(), QString());
|
||||||
|
if (lastRoom.isEmpty() || !m_connection->room(lastRoom)) {
|
||||||
|
setCurrentRoom({});
|
||||||
|
} else {
|
||||||
|
m_currentRoom = nullptr;
|
||||||
|
resolveResource(lastRoom);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UriResolveResult RoomManager::visitUser(User *user, const QString &action)
|
UriResolveResult RoomManager::visitUser(User *user, const QString &action)
|
||||||
@@ -520,32 +521,18 @@ void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom)
|
|||||||
|
|
||||||
Q_EMIT currentSpaceChanged();
|
Q_EMIT currentSpaceChanged();
|
||||||
if (m_connection) {
|
if (m_connection) {
|
||||||
if (spaceId.isEmpty()) {
|
m_lastSpaceConfig.writeEntry(m_connection->userId(), spaceId);
|
||||||
m_lastSpaceConfig.writeEntry(m_connection->userId(), u"Home"_s);
|
|
||||||
} else {
|
|
||||||
m_lastSpaceConfig.writeEntry(m_connection->userId(), spaceId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!setRoom) {
|
if (!setRoom) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We intentionally don't want to open the last room on mobile
|
|
||||||
if (!m_isMobile) {
|
if (!m_isMobile) {
|
||||||
QString configSpaceId = spaceId;
|
if (spaceId.length() > 3) {
|
||||||
// We can't have empty keys in KConfig, so it's stored as "Home"
|
resolveResource(spaceId, "no_join"_L1);
|
||||||
if (spaceId.isEmpty()) {
|
|
||||||
configSpaceId = u"Home"_s;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &lastRoom = m_lastRoomConfig.readEntry(configSpaceId, QString());
|
|
||||||
if (lastRoom.isEmpty()) {
|
|
||||||
if (spaceId != u"DM"_s && spaceId != u"Home"_s) {
|
|
||||||
resolveResource(spaceId, "no_join"_L1);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
resolveResource(lastRoom, "no_join"_L1);
|
visitRoom({}, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -568,16 +555,7 @@ void RoomManager::setCurrentRoom(const QString &roomId)
|
|||||||
|
|
||||||
Q_EMIT currentRoomChanged();
|
Q_EMIT currentRoomChanged();
|
||||||
if (m_connection) {
|
if (m_connection) {
|
||||||
if (roomId.isEmpty()) {
|
m_lastRoomConfig.writeEntry(m_connection->userId(), roomId);
|
||||||
m_lastRoomConfig.deleteEntry(m_currentSpaceId);
|
|
||||||
} else {
|
|
||||||
// We can't have empty keys in KConfig, so name it "Home"
|
|
||||||
if (m_currentSpaceId.isEmpty()) {
|
|
||||||
m_lastRoomConfig.writeEntry(u"Home"_s, roomId);
|
|
||||||
} else {
|
|
||||||
m_lastRoomConfig.writeEntry(m_currentSpaceId, roomId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (roomId.isEmpty()) {
|
if (roomId.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -15,5 +15,4 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
|
|||||||
EmojiPicker.qml
|
EmojiPicker.qml
|
||||||
EmojiDialog.qml
|
EmojiDialog.qml
|
||||||
EmojiTonesPicker.qml
|
EmojiTonesPicker.qml
|
||||||
ImageEditorPage.qml
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -160,6 +160,11 @@ QQC2.Control {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A message has been sent from the chat bar.
|
||||||
|
*/
|
||||||
|
signal messageSent
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
@@ -350,13 +355,11 @@ QQC2.Control {
|
|||||||
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
|
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
|
||||||
onClicked: modelData.trigger()
|
onClicked: modelData.trigger()
|
||||||
|
|
||||||
padding: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.text: modelData.tooltip
|
QQC2.ToolTip.text: modelData.tooltip
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
|
||||||
contentItem: PieProgressBar {
|
PieProgressBar {
|
||||||
visible: modelData.isBusy
|
visible: modelData.isBusy
|
||||||
progress: root.currentRoom.fileUploadingProgress
|
progress: root.currentRoom.fileUploadingProgress
|
||||||
}
|
}
|
||||||
@@ -431,6 +434,7 @@ QQC2.Control {
|
|||||||
repeatTimer.stop();
|
repeatTimer.stop();
|
||||||
root.currentRoom.markAllMessagesAsRead();
|
root.currentRoom.markAllMessagesAsRead();
|
||||||
textField.clear();
|
textField.clear();
|
||||||
|
messageSent();
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatText(format, selectionStart, selectionEnd) {
|
function formatText(format, selectionStart, selectionEnd) {
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ ColumnLayout {
|
|||||||
padding: Kirigami.Units.largeSpacing
|
padding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
contentItem: Image {
|
contentItem: Image {
|
||||||
source: model.url
|
source: model.avatarUrl
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
sourceSize.width: width
|
sourceSize.width: width
|
||||||
sourceSize.height: height
|
sourceSize.height: height
|
||||||
|
|||||||
@@ -36,13 +36,4 @@ FormCard.FormCard {
|
|||||||
NeoChatConfig.save();
|
NeoChatConfig.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FormCard.FormCheckDelegate {
|
|
||||||
text: i18nc("@option:check Enable the matrix feature for audio and video calling", "Calls")
|
|
||||||
checked: NeoChatConfig.calls
|
|
||||||
|
|
||||||
onToggled: {
|
|
||||||
NeoChatConfig.calls = checked;
|
|
||||||
NeoChatConfig.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ target_sources(LibNeoChat PRIVATE
|
|||||||
enums/pushrule.h
|
enums/pushrule.h
|
||||||
enums/roomsortparameter.cpp
|
enums/roomsortparameter.cpp
|
||||||
enums/roomsortorder.h
|
enums/roomsortorder.h
|
||||||
enums/timelinemarkreadcondition.h
|
|
||||||
events/imagepackevent.cpp
|
events/imagepackevent.cpp
|
||||||
events/pollevent.cpp
|
events/pollevent.cpp
|
||||||
jobs/neochatgetcommonroomsjob.cpp
|
jobs/neochatgetcommonroomsjob.cpp
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ AccountManager::AccountManager(bool testMode, QObject *parent)
|
|||||||
loadAccountsFromCache();
|
loadAccountsFromCache();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
auto c = new NeoChatConnection(QUrl(u"https://localhost:1234"_s), this);
|
auto c = new NeoChatConnection(this);
|
||||||
c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
|
c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
|
||||||
m_accountRegistry->add(c);
|
connect(c, &NeoChatConnection::connected, this, [c, this]() {
|
||||||
c->syncLoop();
|
m_accountRegistry->add(c);
|
||||||
|
c->syncLoop();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QQmlEngine>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class TimelineMarkReadCondition
|
|
||||||
*
|
|
||||||
* This class is designed to define the TimelineMarkReadCondition enumeration.
|
|
||||||
*/
|
|
||||||
class TimelineMarkReadCondition : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
QML_UNCREATABLE("")
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief The condition for marking messages as read.
|
|
||||||
*/
|
|
||||||
enum Condition {
|
|
||||||
Never = 0, /**< Messages should never be marked automatically. */
|
|
||||||
Entry, /**< Messages should be marked automatically on entry to the room. */
|
|
||||||
EntryVisible, /**< Messages should be marked automatically on entry to the room if all messages are visible. */
|
|
||||||
Exit, /**< Messages should be marked automatically on exiting the room. */
|
|
||||||
ExitVisible, /**< Messages should be marked automatically on exiting the room if all messages are visible. */
|
|
||||||
};
|
|
||||||
Q_ENUM(Condition);
|
|
||||||
};
|
|
||||||
@@ -435,13 +435,6 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
|||||||
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"_L1].toString());
|
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"_L1].toString());
|
||||||
},
|
},
|
||||||
[prettyPrint](const StateEvent &e) {
|
[prettyPrint](const StateEvent &e) {
|
||||||
if (e.matrixType() == "org.matrix.msc3401.call.member"_L1) {
|
|
||||||
if (e.contentJson().isEmpty()) {
|
|
||||||
return i18nc("[User] left a [voice/video] call", "left a call");
|
|
||||||
} else {
|
|
||||||
return i18nc("[User] joined a [voice/video] call", "joined a call");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
|
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
|
||||||
},
|
},
|
||||||
@@ -641,14 +634,7 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
|
|||||||
}
|
}
|
||||||
return i18n("%1 configured a widget", senderString);
|
return i18n("%1 configured a widget", senderString);
|
||||||
},
|
},
|
||||||
[senderString](const StateEvent &e) {
|
[senderString](const StateEvent &) {
|
||||||
if (e.matrixType() == "org.matrix.msc3401.call.member"_L1) {
|
|
||||||
if (e.contentJson().isEmpty()) {
|
|
||||||
return i18nc("[User] left a [voice/video] call", "%1 left a call", senderString);
|
|
||||||
} else {
|
|
||||||
return i18nc("[User] joined a [voice/video] call", "%1 joined a call", senderString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i18n("%1 updated the state", senderString);
|
return i18n("%1 updated the state", senderString);
|
||||||
},
|
},
|
||||||
[senderString](const PollStartEvent &) {
|
[senderString](const PollStartEvent &) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ struct MessageComponent {
|
|||||||
QString content;
|
QString content;
|
||||||
QVariantMap attributes;
|
QVariantMap attributes;
|
||||||
|
|
||||||
bool operator==(const MessageComponent &right) const
|
int operator==(const MessageComponent &right) const
|
||||||
{
|
{
|
||||||
return type == right.type && content == right.content && attributes == right.attributes;
|
return type == right.type && content == right.content && attributes == right.attributes;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,10 +59,6 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
std::function<bool(const Quotient::RoomEvent *)> NeoChatRoom::m_hiddenFilter = [](const Quotient::RoomEvent *) -> bool {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinState)
|
NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinState)
|
||||||
: Room(connection, std::move(roomId), joinState)
|
: Room(connection, std::move(roomId), joinState)
|
||||||
{
|
{
|
||||||
@@ -373,7 +369,7 @@ const RoomEvent *NeoChatRoom::lastEvent(std::function<bool(const RoomEvent *)> f
|
|||||||
|
|
||||||
void NeoChatRoom::cacheLastEvent()
|
void NeoChatRoom::cacheLastEvent()
|
||||||
{
|
{
|
||||||
auto event = lastEvent(m_hiddenFilter);
|
auto event = lastEvent();
|
||||||
if (event != nullptr) {
|
if (event != nullptr) {
|
||||||
auto &roomLastMessageProvider = RoomLastMessageProvider::self();
|
auto &roomLastMessageProvider = RoomLastMessageProvider::self();
|
||||||
|
|
||||||
@@ -485,6 +481,30 @@ void NeoChatRoom::changeAvatar(const QUrl &localFile)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString msgTypeToString(MessageEventType msgType)
|
||||||
|
{
|
||||||
|
switch (msgType) {
|
||||||
|
case MessageEventType::Text:
|
||||||
|
return "m.text"_L1;
|
||||||
|
case MessageEventType::File:
|
||||||
|
return "m.file"_L1;
|
||||||
|
case MessageEventType::Audio:
|
||||||
|
return "m.audio"_L1;
|
||||||
|
case MessageEventType::Emote:
|
||||||
|
return "m.emote"_L1;
|
||||||
|
case MessageEventType::Image:
|
||||||
|
return "m.image"_L1;
|
||||||
|
case MessageEventType::Video:
|
||||||
|
return "m.video"_L1;
|
||||||
|
case MessageEventType::Notice:
|
||||||
|
return "m.notice"_L1;
|
||||||
|
case MessageEventType::Location:
|
||||||
|
return "m.location"_L1;
|
||||||
|
default:
|
||||||
|
return "m.text"_L1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
|
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
|
||||||
{
|
{
|
||||||
if (eventId.isEmpty() || reaction.isEmpty()) {
|
if (eventId.isEmpty() || reaction.isEmpty()) {
|
||||||
@@ -1214,38 +1234,34 @@ QByteArray NeoChatRoom::getEventJsonSource(const QString &eventId)
|
|||||||
void NeoChatRoom::openEventMediaExternally(const QString &eventId)
|
void NeoChatRoom::openEventMediaExternally(const QString &eventId)
|
||||||
{
|
{
|
||||||
const auto evtIt = findInTimeline(eventId);
|
const auto evtIt = findInTimeline(eventId);
|
||||||
if (evtIt == messageEvents().rend()) {
|
if (evtIt != messageEvents().rend() && is<RoomMessageEvent>(**evtIt)) {
|
||||||
return;
|
const auto event = evtIt->viewAs<RoomMessageEvent>();
|
||||||
}
|
if (event->has<EventContent::FileContent>()) {
|
||||||
|
const auto transferInfo = cachedFileTransferInfo(event);
|
||||||
// TODO: Also allow stickers here, once that's fixed in libQuotient
|
if (transferInfo.completed()) {
|
||||||
if (!is<RoomMessageEvent>(**evtIt) || !evtIt->viewAs<RoomMessageEvent>()->has<EventContent::FileContentBase>()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto transferInfo = cachedFileTransferInfo(evtIt->viewAs<RoomEvent>());
|
|
||||||
if (transferInfo.completed()) {
|
|
||||||
UrlHelper helper;
|
|
||||||
helper.openUrl(transferInfo.localPath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
downloadFile(eventId,
|
|
||||||
QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/'
|
|
||||||
+ evtIt->event()->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId)));
|
|
||||||
connect(
|
|
||||||
this,
|
|
||||||
&Room::fileTransferCompleted,
|
|
||||||
this,
|
|
||||||
[this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
|
|
||||||
Q_UNUSED(localFile);
|
|
||||||
Q_UNUSED(fileMetadata);
|
|
||||||
if (id == eventId) {
|
|
||||||
auto transferInfo = fileTransferInfo(eventId);
|
|
||||||
UrlHelper helper;
|
UrlHelper helper;
|
||||||
helper.openUrl(transferInfo.localPath);
|
helper.openUrl(transferInfo.localPath);
|
||||||
|
} else {
|
||||||
|
downloadFile(eventId,
|
||||||
|
QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/'
|
||||||
|
+ event->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId)));
|
||||||
|
connect(
|
||||||
|
this,
|
||||||
|
&Room::fileTransferCompleted,
|
||||||
|
this,
|
||||||
|
[this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
|
||||||
|
Q_UNUSED(localFile);
|
||||||
|
Q_UNUSED(fileMetadata);
|
||||||
|
if (id == eventId) {
|
||||||
|
auto transferInfo = fileTransferInfo(eventId);
|
||||||
|
UrlHelper helper;
|
||||||
|
helper.openUrl(transferInfo.localPath);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::copyEventMedia(const QString &eventId)
|
void NeoChatRoom::copyEventMedia(const QString &eventId)
|
||||||
@@ -1726,9 +1742,4 @@ QString NeoChatRoom::rootIdForThread(const QString &eventId) const
|
|||||||
return rootId;
|
return rootId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::setHiddenFilter(std::function<bool(const Quotient::RoomEvent *)> hiddenFilter)
|
|
||||||
{
|
|
||||||
NeoChatRoom::m_hiddenFilter = hiddenFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "moc_neochatroom.cpp"
|
#include "moc_neochatroom.cpp"
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ public:
|
|||||||
* responsibility of the caller to ensure that they only ask for objects
|
* responsibility of the caller to ensure that they only ask for objects
|
||||||
* for real senders.
|
* for real senders.
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE NeochatRoomMember *qmlSafeMember(const QString &memberId);
|
NeochatRoomMember *qmlSafeMember(const QString &memberId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Pin a message in the room.
|
* @brief Pin a message in the room.
|
||||||
@@ -587,8 +587,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
Q_INVOKABLE QString rootIdForThread(const QString &eventId) const;
|
Q_INVOKABLE QString rootIdForThread(const QString &eventId) const;
|
||||||
|
|
||||||
static void setHiddenFilter(std::function<bool(const Quotient::RoomEvent *)> hiddenFilter);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_visible = false;
|
bool m_visible = false;
|
||||||
|
|
||||||
@@ -620,7 +618,6 @@ private:
|
|||||||
void cleanupExtraEvent(const QString &eventId);
|
void cleanupExtraEvent(const QString &eventId);
|
||||||
|
|
||||||
std::unordered_map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
|
std::unordered_map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
|
||||||
static std::function<bool(const Quotient::RoomEvent *)> m_hiddenFilter;
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void updatePushNotificationState(QString type);
|
void updatePushNotificationState(QString type);
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ ColumnLayout {
|
|||||||
*/
|
*/
|
||||||
required property NeoChatRoom room
|
required property NeoChatRoom room
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The canonical alias of the room, if it exists. Otherwise falls back to the first available alias.
|
|
||||||
*/
|
|
||||||
readonly property var roomAlias: room.aliases[0]
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -78,8 +73,8 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
font: Kirigami.Theme.smallFont
|
font: Kirigami.Theme.smallFont
|
||||||
textFormat: TextEdit.PlainText
|
textFormat: TextEdit.PlainText
|
||||||
visible: root.room && root.roomAlias
|
visible: root.room && root.room.canonicalAlias
|
||||||
text: root.room && root.roomAlias ? root.roomAlias : ""
|
text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : ""
|
||||||
color: Kirigami.Theme.disabledTextColor
|
color: Kirigami.Theme.disabledTextColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -718,15 +718,10 @@ QString TextHandler::customMarkdownToHtml(const QString &stringIn)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're inside a code block, ignore and move the search past this code block
|
// If we're inside a code block, ignore and move the search past the code block
|
||||||
const bool validCodeBlock = beginCodeBlockTag != -1 && endCodeBlockTag != -1;
|
const bool validCodeBlock = beginCodeBlockTag != -1 && endCodeBlockTag != -1;
|
||||||
if (validCodeBlock && pos > beginCodeBlockTag && pos < endCodeBlockTag) {
|
if (validCodeBlock && pos > beginCodeBlockTag && pos < endCodeBlockTag) {
|
||||||
lastPos = endCodeBlockTag + 7;
|
lastPos = endCodeBlockTag + 5;
|
||||||
|
|
||||||
// since we moved past this code block, make sure to update the indices for the next one
|
|
||||||
beginCodeBlockTag = buffer.indexOf(u"<code>"_s, lastPos + 1);
|
|
||||||
endCodeBlockTag = buffer.indexOf(u"</code>"_s, beginCodeBlockTag + 1);
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,24 +731,20 @@ QString TextHandler::customMarkdownToHtml(const QString &stringIn)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace the beginning syntax
|
// Replace the beginning syntax
|
||||||
buffer.replace(pos, syntax.length(), beginTag);
|
buffer.replace(pos, 2, beginTag);
|
||||||
|
|
||||||
// Update positions and re-search since the underlying text buffer changed
|
// Update positions and re-search since the underlying text buffer changed
|
||||||
nextPos = buffer.indexOf(syntax, pos + 1);
|
nextPos = buffer.indexOf(syntax, pos + 1);
|
||||||
|
beginCodeBlockTag = buffer.indexOf(u"<code>"_s, pos + 1);
|
||||||
|
endCodeBlockTag = buffer.indexOf(u"</code>"_s, beginCodeBlockTag + 1);
|
||||||
|
|
||||||
// Now replace the end syntax
|
// Now replace the end syntax
|
||||||
buffer.replace(nextPos, syntax.length(), endTag);
|
buffer.replace(nextPos, 2, endTag);
|
||||||
|
|
||||||
// If we have begun checking spoilers past our current code block, make sure we're in the next one (if it exists)
|
|
||||||
if (nextPos > endCodeBlockTag) {
|
|
||||||
beginCodeBlockTag = buffer.indexOf(u"<code>"_s, nextPos + 1);
|
|
||||||
endCodeBlockTag = buffer.indexOf(u"</code>"_s, beginCodeBlockTag + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the search pointer past this point.
|
// Move the search pointer past this point.
|
||||||
// Not technically needed in most cases since we replaced the original tag, but needed for code blocks
|
// Not technically needed in most cases since we replaced the original tag, but needed for code blocks
|
||||||
// which still have the characters.
|
// which still have the characters.
|
||||||
lastPos = nextPos + syntax.length();
|
lastPos = nextPos + 2;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
|
||||||
# SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
|
|
||||||
qt_add_library(MessageContent STATIC)
|
|
||||||
ecm_add_qml_module(MessageContent GENERATE_PLUGIN_SOURCE
|
|
||||||
URI org.kde.neochat.messagecontent
|
|
||||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/messagecontent
|
|
||||||
QML_FILES
|
|
||||||
BaseMessageComponentChooser.qml
|
|
||||||
MessageComponentChooser.qml
|
|
||||||
ReplyMessageComponentChooser.qml
|
|
||||||
AuthorComponent.qml
|
|
||||||
AudioComponent.qml
|
|
||||||
ChatBarComponent.qml
|
|
||||||
CodeComponent.qml
|
|
||||||
EncryptedComponent.qml
|
|
||||||
FetchButtonComponent.qml
|
|
||||||
FileComponent.qml
|
|
||||||
ImageComponent.qml
|
|
||||||
ItineraryComponent.qml
|
|
||||||
ItineraryReservationComponent.qml
|
|
||||||
JourneySectionStopDelegateLineSegment.qml
|
|
||||||
TransportIcon.qml
|
|
||||||
FoodReservationComponent.qml
|
|
||||||
TrainReservationComponent.qml
|
|
||||||
FlightReservationComponent.qml
|
|
||||||
HotelReservationComponent.qml
|
|
||||||
LinkPreviewComponent.qml
|
|
||||||
LinkPreviewLoadComponent.qml
|
|
||||||
LiveLocationComponent.qml
|
|
||||||
LoadComponent.qml
|
|
||||||
LocationComponent.qml
|
|
||||||
MimeComponent.qml
|
|
||||||
PdfPreviewComponent.qml
|
|
||||||
PollComponent.qml
|
|
||||||
QuoteComponent.qml
|
|
||||||
ReactionComponent.qml
|
|
||||||
ReplyAuthorComponent.qml
|
|
||||||
ReplyButtonComponent.qml
|
|
||||||
ReplyComponent.qml
|
|
||||||
StateComponent.qml
|
|
||||||
TextComponent.qml
|
|
||||||
ThreadBodyComponent.qml
|
|
||||||
VideoComponent.qml
|
|
||||||
SOURCES
|
|
||||||
contentprovider.cpp
|
|
||||||
mediasizehelper.cpp
|
|
||||||
pollhandler.cpp
|
|
||||||
models/itinerarymodel.cpp
|
|
||||||
models/linemodel.cpp
|
|
||||||
models/messagecontentmodel.cpp
|
|
||||||
models/pollanswermodel.cpp
|
|
||||||
models/reactionmodel.cpp
|
|
||||||
models/threadmodel.cpp
|
|
||||||
RESOURCES
|
|
||||||
images/bike.svg
|
|
||||||
images/bus.svg
|
|
||||||
images/cablecar.svg
|
|
||||||
images/car.svg
|
|
||||||
images/coach.svg
|
|
||||||
images/couchettecar.svg
|
|
||||||
images/elevator.svg
|
|
||||||
images/escalator.svg
|
|
||||||
images/ferry.svg
|
|
||||||
images/flight.svg
|
|
||||||
images/foodestablishment.svg
|
|
||||||
images/funicular.svg
|
|
||||||
images/longdistancetrain.svg
|
|
||||||
images/rapidtransit.svg
|
|
||||||
images/seat.svg
|
|
||||||
images/shuttle.svg
|
|
||||||
images/sleepingcar.svg
|
|
||||||
images/stairs.svg
|
|
||||||
images/subway.svg
|
|
||||||
images/taxi.svg
|
|
||||||
images/train.svg
|
|
||||||
images/tramway.svg
|
|
||||||
images/transfer.svg
|
|
||||||
images/wait.svg
|
|
||||||
images/walk.svg
|
|
||||||
DEPENDENCIES
|
|
||||||
QtQuick
|
|
||||||
)
|
|
||||||
|
|
||||||
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
|
|
||||||
|
|
||||||
ecm_qt_declare_logging_category(MessageContent
|
|
||||||
HEADER "messagemodel_logging.h"
|
|
||||||
IDENTIFIER "Message"
|
|
||||||
CATEGORY_NAME "org.kde.neochat.messagemodel"
|
|
||||||
DESCRIPTION "Neochat: messagemodel"
|
|
||||||
DEFAULT_SEVERITY Info
|
|
||||||
EXPORT NEOCHAT
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(MessageContent PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
|
|
||||||
target_link_libraries(MessageContent PRIVATE
|
|
||||||
Qt::Core
|
|
||||||
Qt::Quick
|
|
||||||
Qt::QuickControls2
|
|
||||||
KF6::Kirigami
|
|
||||||
LibNeoChat
|
|
||||||
)
|
|
||||||
|
|
||||||
if(NOT ANDROID)
|
|
||||||
target_link_libraries(MessageContent PUBLIC KF6::SyntaxHighlighting)
|
|
||||||
endif()
|
|
||||||
@@ -14,10 +14,4 @@ ecm_add_qml_module(RoomInfo GENERATE_PLUGIN_SOURCE
|
|||||||
LocationsPage.qml
|
LocationsPage.qml
|
||||||
RoomPinnedMessagesPage.qml
|
RoomPinnedMessagesPage.qml
|
||||||
RoomSearchPage.qml
|
RoomSearchPage.qml
|
||||||
SOURCES
|
|
||||||
locationhelper.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(RoomInfo PRIVATE
|
|
||||||
Qt::Core
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -125,17 +125,12 @@ KirigamiComponents.ConvergentContextMenu {
|
|||||||
QQC2.Action {
|
QQC2.Action {
|
||||||
text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID") : i18nc("@action:inmenu", "Copy Room Address")
|
text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID") : i18nc("@action:inmenu", "Copy Room Address")
|
||||||
icon.name: "edit-copy"
|
icon.name: "edit-copy"
|
||||||
onTriggered: {
|
onTriggered: if (room.isDirectChat()) {
|
||||||
// The canonical alias (if it exists) otherwise the first available alias
|
Clipboard.saveText(room.directChatRemoteMember.id);
|
||||||
const firstAlias = room.aliases[0];
|
} else if (room.canonicalAlias.length === 0) {
|
||||||
|
Clipboard.saveText(room.id);
|
||||||
if (room.isDirectChat()) {
|
} else {
|
||||||
Clipboard.saveText(room.directChatRemoteMember.id);
|
Clipboard.saveText(room.canonicalAlias);
|
||||||
} else if (!firstAlias) {
|
|
||||||
Clipboard.saveText(room.id);
|
|
||||||
} else {
|
|
||||||
Clipboard.saveText(firstAlias);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,22 +17,13 @@ import org.kde.neochat
|
|||||||
Kirigami.Page {
|
Kirigami.Page {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Kirigami.ColumnView.interactiveResizeEnabled: true
|
/**
|
||||||
Kirigami.ColumnView.minimumWidth: _private.collapsedSize + spaceDrawer.width + 1
|
* @brief The current width of the room list.
|
||||||
Kirigami.ColumnView.maximumWidth: _private.defaultWidth + spaceDrawer.width + 1
|
*
|
||||||
Kirigami.ColumnView.onInteractiveResizingChanged: {
|
* @note Other objects can access the value but the private function makes sure
|
||||||
if (!Kirigami.ColumnView.interactiveResizing && collapsed) {
|
* that only the internal members can modify it.
|
||||||
Kirigami.ColumnView.preferredWidth = root.Kirigami.ColumnView.minimumWidth;
|
*/
|
||||||
}
|
readonly property int currentWidth: _private.currentWidth + spaceDrawer.width + 1
|
||||||
}
|
|
||||||
Kirigami.ColumnView.preferredWidth: _private.currentWidth + spaceDrawer.width + 1
|
|
||||||
Kirigami.ColumnView.onPreferredWidthChanged: {
|
|
||||||
if (width > _private.collapseWidth) {
|
|
||||||
NeoChatConfig.collapsed = false;
|
|
||||||
} else if (Kirigami.ColumnView.interactiveResizing) {
|
|
||||||
NeoChatConfig.collapsed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
required property NeoChatConnection connection
|
required property NeoChatConnection connection
|
||||||
|
|
||||||
@@ -40,6 +31,10 @@ Kirigami.Page {
|
|||||||
|
|
||||||
signal search
|
signal search
|
||||||
|
|
||||||
|
onCurrentWidthChanged: pageStack.defaultColumnWidth = root.currentWidth
|
||||||
|
Component.onCompleted: pageStack.defaultColumnWidth = root.currentWidth
|
||||||
|
|
||||||
|
|
||||||
onCollapsedChanged: {
|
onCollapsedChanged: {
|
||||||
if (collapsed) {
|
if (collapsed) {
|
||||||
RoomManager.sortFilterRoomTreeModel.filterText = "";
|
RoomManager.sortFilterRoomTreeModel.filterText = "";
|
||||||
@@ -249,6 +244,49 @@ Kirigami.Page {
|
|||||||
sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
|
sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
parent: applicationWindow().overlay.parent
|
||||||
|
|
||||||
|
x: root.currentWidth - width / 2
|
||||||
|
width: Kirigami.Units.smallSpacing * 2
|
||||||
|
z: root.z + 1
|
||||||
|
enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35
|
||||||
|
visible: enabled
|
||||||
|
cursorShape: Qt.SplitHCursor
|
||||||
|
|
||||||
|
property int _lastX
|
||||||
|
|
||||||
|
onPressed: mouse => {
|
||||||
|
_lastX = mouse.x;
|
||||||
|
}
|
||||||
|
onPositionChanged: mouse => {
|
||||||
|
if (_lastX == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mouse.x > _lastX) {
|
||||||
|
// we moved to the right
|
||||||
|
if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) {
|
||||||
|
// Here we get back directly to a more wide mode.
|
||||||
|
_private.currentWidth = _private.defaultWidth;
|
||||||
|
NeoChatConfig.collapsed = false;
|
||||||
|
} else if (_private.currentWidth >= _private.collapseWidth) {
|
||||||
|
// Increase page width
|
||||||
|
_private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX));
|
||||||
|
}
|
||||||
|
} else if (mouse.x < _lastX) {
|
||||||
|
const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
|
||||||
|
if (tmpWidth < _private.collapseWidth) {
|
||||||
|
_private.currentWidth = Qt.binding(() => _private.collapsedSize);
|
||||||
|
NeoChatConfig.collapsed = true;
|
||||||
|
} else {
|
||||||
|
_private.currentWidth = tmpWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: userInfo
|
id: userInfo
|
||||||
UserInfo {
|
UserInfo {
|
||||||
|
|||||||
@@ -38,14 +38,26 @@ QQC2.ItemDelegate {
|
|||||||
Keys.onSpacePressed: root.treeView.toggleExpanded(row)
|
Keys.onSpacePressed: root.treeView.toggleExpanded(row)
|
||||||
|
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
spacing: 0
|
spacing: Kirigami.Units.largeSpacing
|
||||||
Kirigami.ListSectionHeader {
|
|
||||||
|
Kirigami.Heading {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
visible: !root.collapsed
|
||||||
|
|
||||||
|
opacity: 0.7
|
||||||
|
level: 5
|
||||||
|
type: Kirigami.Heading.Primary
|
||||||
|
text: root.collapsed ? "" : model.displayName
|
||||||
|
elide: Text.ElideRight
|
||||||
|
|
||||||
|
// we override the Primary type's font weight (DemiBold) for Bold for contrast with small text
|
||||||
|
font.weight: Font.Bold
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: !root.collapsed
|
visible: !root.collapsed
|
||||||
horizontalPadding: 0
|
Layout.alignment: Qt.AlignVCenter
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
text: root.collapsed ? "" : root.displayName
|
|
||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
id: collapseButton
|
id: collapseButton
|
||||||
|
|||||||
@@ -95,19 +95,31 @@ RowLayout {
|
|||||||
window: QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow
|
window: QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
|
||||||
display: QQC2.Button.IconOnly
|
|
||||||
action: Kirigami.Action {
|
|
||||||
text: i18n("Open Settings")
|
|
||||||
icon.name: "settings-configure-symbolic"
|
|
||||||
onTriggered: {
|
|
||||||
NeoChatSettingsView.open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: text
|
Kirigami.ActionToolBar {
|
||||||
QQC2.ToolTip.visible: hovered
|
alignment: Qt.AlignRight
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
display: QQC2.Button.IconOnly
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredWidth: maximumContentWidth
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Switch User")
|
||||||
|
icon.name: "system-switch-user"
|
||||||
|
shortcut: "Ctrl+U"
|
||||||
|
onTriggered: accountSwitchDialog.createObject(QQC2.Overlay.overlay, {
|
||||||
|
connection: root.connection
|
||||||
|
}).open();
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Open Settings")
|
||||||
|
icon.name: "settings-configure-symbolic"
|
||||||
|
onTriggered: {
|
||||||
|
NeoChatSettingsView.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ FormCard.FormCardPage {
|
|||||||
icon.name: "document-save-symbolic"
|
icon.name: "document-save-symbolic"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!root.connection.setAvatar(avatar.source)) {
|
if (!root.connection.setAvatar(avatar.source)) {
|
||||||
(root.Window.window as Kirigami.ApplicationWindow).showPassiveNotification("The Avatar could not be set");
|
showPassiveNotification("The Avatar could not be set");
|
||||||
}
|
}
|
||||||
if (root.connection.localUser.displayName !== name.text) {
|
if (root.connection.localUser.displayName !== name.text) {
|
||||||
root.connection.localUser.rename(name.text);
|
root.connection.localUser.rename(name.text);
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ ecm_add_qml_module(Settings GENERATE_PLUGIN_SOURCE
|
|||||||
ExportKeysDialog.qml
|
ExportKeysDialog.qml
|
||||||
RoomSortParameterDialog.qml
|
RoomSortParameterDialog.qml
|
||||||
RoomProfile.qml
|
RoomProfile.qml
|
||||||
RoomAdvancedPage.qml
|
|
||||||
SOURCES
|
SOURCES
|
||||||
colorschemer.cpp
|
colorschemer.cpp
|
||||||
threepidaddhelper.cpp
|
threepidaddhelper.cpp
|
||||||
|
|||||||
@@ -134,45 +134,9 @@ FormCard.FormCardPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
FormCard.FormHeader {
|
FormCard.FormHeader {
|
||||||
title: i18nc("@title", "Timeline")
|
title: i18n("Timeline Events")
|
||||||
}
|
}
|
||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
FormCard.FormComboBoxDelegate {
|
|
||||||
id: markAsReadCombo
|
|
||||||
text: i18n("Mark messages as read when:")
|
|
||||||
textRole: "name"
|
|
||||||
valueRole: "value"
|
|
||||||
model: [
|
|
||||||
{
|
|
||||||
name: i18n("Never"),
|
|
||||||
value: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: i18nc("@item:inlistbox As in mark messages in the room as read when entering the room", "Entering the room"),
|
|
||||||
value: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: i18nc("@item:inlistbox As in mark messages in the room as read when entering the room and all messages are visible on screen", "Entering the room and all unread messages are visible"),
|
|
||||||
value: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: i18nc("@item:inlistbox As in mark messages in the room as read when exiting the room", "Exiting the room"),
|
|
||||||
value: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: i18nc("@item:inlistbox As in mark messages in the room as read when exiting the room and all messages are visible on screen", "Exiting the room and all unread messages are visible"),
|
|
||||||
value: 4
|
|
||||||
}
|
|
||||||
]
|
|
||||||
Component.onCompleted: currentIndex = NeoChatConfig.markReadCondition
|
|
||||||
onCurrentValueChanged: NeoChatConfig.markReadCondition = currentValue
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormDelegateSeparator {
|
|
||||||
above: markAsReadCombo
|
|
||||||
below: showDeletedMessages
|
|
||||||
}
|
|
||||||
|
|
||||||
FormCard.FormCheckDelegate {
|
FormCard.FormCheckDelegate {
|
||||||
id: showDeletedMessages
|
id: showDeletedMessages
|
||||||
text: i18n("Show deleted messages")
|
text: i18n("Show deleted messages")
|
||||||
@@ -345,16 +309,8 @@ FormCard.FormCardPage {
|
|||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
FormCard.FormButtonDelegate {
|
FormCard.FormButtonDelegate {
|
||||||
icon.name: "kt-restore-defaults-symbolic"
|
icon.name: "kt-restore-defaults-symbolic"
|
||||||
text: i18nc("@action:button", "Reset all configuration values to their default")
|
text: i18nc("@action:button", "Reset All Configuration Values to Their Default")
|
||||||
onClicked: resetDialog.open()
|
onClicked: Controller.revertToDefaultConfig()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kirigami.PromptDialog {
|
|
||||||
id: resetDialog
|
|
||||||
title: i18nc("@title:dialog", "Reset Configuration")
|
|
||||||
subtitle: i18nc("@info", "Do you really want to reset all options to their default values?")
|
|
||||||
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
|
||||||
onAccepted: Controller.revertToDefaultConfig()
|
|
||||||
anchors.centerIn: QQC2.Overlay.overlay
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user