Compare commits
106 Commits
work/redst
...
release/25
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58f5a4bbd3 | ||
|
|
bceeef14de | ||
|
|
bae20aaf84 | ||
|
|
135aa74209 | ||
|
|
2ea6e0425a | ||
|
|
fe55ff19d2 | ||
|
|
c35bdc0592 | ||
|
|
c6fa5a10dd | ||
|
|
f956c33b82 | ||
|
|
0b9295e67e | ||
|
|
0fdade83e0 | ||
|
|
6a25945131 | ||
|
|
218222bc58 | ||
|
|
4300bba804 | ||
|
|
13f766d166 | ||
|
|
fd4e701c51 | ||
|
|
d8ff639374 | ||
|
|
4a50281152 | ||
|
|
acc9289d06 | ||
|
|
235bd21eaf | ||
|
|
c7c1c8fd5c | ||
|
|
fdbee5a508 | ||
|
|
fb58003451 | ||
|
|
debbe8e478 | ||
|
|
0ce86e5a08 | ||
|
|
6935d887c4 | ||
|
|
963346e0f4 | ||
|
|
e37dd88c43 | ||
|
|
2aacb640c8 | ||
|
|
2d63a92702 | ||
|
|
de6731cfda | ||
|
|
7b3c40757c | ||
|
|
87f243ba8b | ||
|
|
192cdc1ff3 | ||
|
|
c72f77f7b6 | ||
|
|
d978f8de50 | ||
|
|
cf216268ab | ||
|
|
f9741a66c4 | ||
|
|
aac3bfda88 | ||
|
|
2722a6f2f0 | ||
|
|
ecf4b85f00 | ||
|
|
ec1413d1ce | ||
|
|
0b7a6df0a3 | ||
|
|
040efa46f9 | ||
|
|
ef4b41e6f8 | ||
|
|
80f81847f4 | ||
|
|
681a3c4036 | ||
|
|
fd27c70b85 | ||
|
|
435124ffe8 | ||
|
|
e2ca698389 | ||
|
|
7e9cfbedc9 | ||
|
|
eeed8a7277 | ||
|
|
aa8c515432 | ||
|
|
ba30014d40 | ||
|
|
d131030d47 | ||
|
|
23a0d91627 | ||
|
|
11d5a37ffe | ||
|
|
256a8e5a1e | ||
|
|
b608921d43 | ||
|
|
45a3984bf9 | ||
|
|
570e0425e9 | ||
|
|
09ed1bd616 | ||
|
|
3201426886 | ||
|
|
96dc83d807 | ||
|
|
ab1fb8ae97 | ||
|
|
c5a4b2a50a | ||
|
|
17fdad3afd | ||
|
|
64bc2691cb | ||
|
|
c7df34a9c8 | ||
|
|
1525c74b10 | ||
|
|
58b85622c5 | ||
|
|
796470d0e0 | ||
|
|
3a43d99575 | ||
|
|
a62798ef1e | ||
|
|
349d0c5f5f | ||
|
|
c917fc0166 | ||
|
|
a4f2d8fca1 | ||
|
|
fd18f88adf | ||
|
|
546694b08e | ||
|
|
699026fc2f | ||
|
|
c61c2d7437 | ||
|
|
56babbc1c5 | ||
|
|
74b3e703c1 | ||
|
|
f620221113 | ||
|
|
896c001430 | ||
|
|
defee77c96 | ||
|
|
4328ab8e89 | ||
|
|
54b081abba | ||
|
|
29686608e1 | ||
|
|
b720ecf29d | ||
|
|
fc859d679a | ||
|
|
3595ad9293 | ||
|
|
73f8ebc54e | ||
|
|
19cf534acd | ||
|
|
9b86088e26 | ||
|
|
a93117fcd6 | ||
|
|
ee20c90498 | ||
|
|
860a2267d5 | ||
|
|
cb9b2648ca | ||
|
|
1e798b6c15 | ||
|
|
124ffba5e0 | ||
|
|
3aaaa610df | ||
|
|
18e883834c | ||
|
|
6acbd2dffd | ||
|
|
dc5c27aa2d | ||
|
|
26774bbe56 |
@@ -1,2 +0,0 @@
|
|||||||
[General]
|
|
||||||
disableUnqualifiedAccess = "i18nc,xi18nc,i18ncp,i18n"
|
|
||||||
@@ -4,5 +4,3 @@
|
|||||||
[BlueprintSettings]
|
[BlueprintSettings]
|
||||||
kde/applications/neochat.packageAppx=True
|
kde/applications/neochat.packageAppx=True
|
||||||
libs/qt.qtMajorVersion=6
|
libs/qt.qtMajorVersion=6
|
||||||
; Remove once KNotifications v6.19 releases, to make Android notifications work again
|
|
||||||
kde/frameworks/tier3/knotifications.version=master
|
|
||||||
|
|||||||
@@ -20,16 +20,8 @@
|
|||||||
"--talk-name=org.kde.kwalletd5",
|
"--talk-name=org.kde.kwalletd5",
|
||||||
"--talk-name=org.kde.StatusNotifierWatcher",
|
"--talk-name=org.kde.StatusNotifierWatcher",
|
||||||
"--talk-name=org.freedesktop.secrets",
|
"--talk-name=org.freedesktop.secrets",
|
||||||
"--talk-name=org.kde.kuiserver",
|
|
||||||
"--own-name=org.kde.StatusNotifierItem-2-2"
|
"--own-name=org.kde.StatusNotifierItem-2-2"
|
||||||
],
|
],
|
||||||
"cleanup": [
|
|
||||||
"/include",
|
|
||||||
"/lib/*.a",
|
|
||||||
"/lib/cmake",
|
|
||||||
"/lib/pkgconfig",
|
|
||||||
"/share/ndk-modules"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"name": "kirigamiaddons",
|
"name": "kirigamiaddons",
|
||||||
@@ -44,22 +36,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "opencv",
|
|
||||||
"config-opts": [
|
|
||||||
"-DBUILD_TESTS=OFF",
|
|
||||||
"-DWITH_GTK=OFF",
|
|
||||||
"-DBUILD_LIST=core,imgproc"
|
|
||||||
],
|
|
||||||
"buildsystem": "cmake-ninja",
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/opencv/opencv"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"builddir": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "kquickimageeditor",
|
"name": "kquickimageeditor",
|
||||||
"config-opts": [
|
"config-opts": [
|
||||||
@@ -106,8 +82,8 @@
|
|||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.7.tar.xz",
|
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.6.tar.xz",
|
||||||
"sha256": "6b452e4750590a2b5617adc40026f28d2f4903de15f1250e1d1c40bfd68ed55e",
|
"sha256": "747b8c175be108c880d3adfb9c3537ea66e520e4ad2dccf5dce58003aeeca090",
|
||||||
"x-checker-data": {
|
"x-checker-data": {
|
||||||
"type": "gnome",
|
"type": "gnome",
|
||||||
"name": "libsecret",
|
"name": "libsecret",
|
||||||
@@ -177,20 +153,16 @@
|
|||||||
"name": "kunifiedpush",
|
"name": "kunifiedpush",
|
||||||
"buildsystem": "cmake-ninja",
|
"buildsystem": "cmake-ninja",
|
||||||
"builddir": true,
|
"builddir": true,
|
||||||
"config-opts": [
|
|
||||||
"-DENABLE_TESTING=OFF",
|
|
||||||
"-DKUNIFIEDPUSH_CLIENT_ONLY=ON"
|
|
||||||
],
|
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://download.kde.org/stable/release-service/25.08.0/src/kunifiedpush-25.08.0.tar.xz",
|
"url": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-1.0.0.tar.xz",
|
||||||
"sha256": "846db6ffc7d93f6afea7ce0d5a9f10b52792157ceb593856542279f4197f3518",
|
"sha256": "2ddeba21306d0307114ec50a2c38159ec62359f9fc6cdd58da30a369fbd550cf",
|
||||||
"x-checker-data": {
|
"x-checker-data": {
|
||||||
"type": "anitya",
|
"type": "anitya",
|
||||||
"project-id": 8763,
|
"project-id": 375055,
|
||||||
"stable-only": true,
|
"stable-only": true,
|
||||||
"url-template": "https://download.kde.org/stable/release-service/$version/src/kunifiedpush-$version.tar.xz"
|
"url-template": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-$version.tar.xz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -43,4 +43,3 @@ Dependencies:
|
|||||||
Options:
|
Options:
|
||||||
per-test-timeout: 90
|
per-test-timeout: 90
|
||||||
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows']
|
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows']
|
||||||
run-qmllint: True
|
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ 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 "08")
|
||||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
set(RELEASE_SERVICE_VERSION_MICRO "3")
|
||||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||||
|
|
||||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||||
|
|
||||||
set(KF_MIN_VERSION "6.17")
|
set(KF_MIN_VERSION "6.16")
|
||||||
set(QT_MIN_VERSION "6.9")
|
set(QT_MIN_VERSION "6.5")
|
||||||
|
|
||||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
|||||||
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(QuotientQt6 0.9.3)
|
find_package(QuotientQt6 0.9.1)
|
||||||
set_package_properties(QuotientQt6 PROPERTIES
|
set_package_properties(QuotientQt6 PROPERTIES
|
||||||
TYPE REQUIRED
|
TYPE REQUIRED
|
||||||
DESCRIPTION "Qt wrapper around Matrix API"
|
DESCRIPTION "Qt wrapper around Matrix API"
|
||||||
|
|||||||
@@ -88,9 +88,3 @@ path = "memorytests/memtest-sync.json"
|
|||||||
precedence = "aggregate"
|
precedence = "aggregate"
|
||||||
SPDX-FileCopyrightText = "2024 James Graham <james.h.graham@protonmail.com>"
|
SPDX-FileCopyrightText = "2024 James Graham <james.h.graham@protonmail.com>"
|
||||||
SPDX-License-Identifier = "BSD-2-Clause"
|
SPDX-License-Identifier = "BSD-2-Clause"
|
||||||
|
|
||||||
[[annotations]]
|
|
||||||
path = ".contextProperties.ini"
|
|
||||||
precedence = "aggregate"
|
|
||||||
SPDX-FileCopyrightText = "2025 Tobias Fella <tobias.fella@kde.org>"
|
|
||||||
SPDX-License-Identifier = "BSD-2-Clause"
|
|
||||||
|
|||||||
@@ -92,15 +92,3 @@ ecm_add_test(
|
|||||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
LINK_LIBRARIES neochat Qt::Test neochat_server
|
||||||
TEST_NAME actionstest
|
TEST_NAME actionstest
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_add_test(
|
|
||||||
servernoticestest.cpp
|
|
||||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
|
||||||
TEST_NAME servernoticestest
|
|
||||||
)
|
|
||||||
|
|
||||||
ecm_add_test(
|
|
||||||
roommanagertest.cpp
|
|
||||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
|
||||||
TEST_NAME roommanagertest
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ void ActionsTest::testActions_data()
|
|||||||
QTest::addColumn<std::optional<QString>>("resultText");
|
QTest::addColumn<std::optional<QString>>("resultText");
|
||||||
QTest::addColumn<std::optional<Quotient::RoomMessageEvent::MsgType>>("type");
|
QTest::addColumn<std::optional<Quotient::RoomMessageEvent::MsgType>>("type");
|
||||||
|
|
||||||
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\_(ツ)_/¯ Hello"_s)
|
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\\\_(ツ)\\_/¯ Hello"_s)
|
||||||
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
||||||
QTest::newRow("lenny") << u"/lenny Hello"_s << std::make_optional(u"( ͡° ͜ʖ ͡°) Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
QTest::newRow("lenny") << u"/lenny Hello"_s << std::make_optional(u"( ͡° ͜ʖ ͡°) Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
||||||
QTest::newRow("tableflip") << u"/tableflip Hello"_s << std::make_optional(u"(╯°□°)╯︵ ┻━┻ Hello"_s)
|
QTest::newRow("tableflip") << u"/tableflip Hello"_s << std::make_optional(u"(╯°□°)╯︵ ┻━┻ Hello"_s)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTest>
|
#include <QTest>
|
||||||
|
|
||||||
#include <QSignalSpy>
|
|
||||||
#include <Quotient/roommember.h>
|
#include <Quotient/roommember.h>
|
||||||
#include <Quotient/syncdata.h>
|
#include <Quotient/syncdata.h>
|
||||||
#include <qtestcase.h>
|
#include <qtestcase.h>
|
||||||
@@ -33,7 +32,6 @@ private Q_SLOTS:
|
|||||||
void noRoom();
|
void noRoom();
|
||||||
void badParent();
|
void badParent();
|
||||||
void reply();
|
void reply();
|
||||||
void replyMissingUser();
|
|
||||||
void edit();
|
void edit();
|
||||||
void attachment();
|
void attachment();
|
||||||
};
|
};
|
||||||
@@ -104,33 +102,6 @@ void ChatBarCacheTest::reply()
|
|||||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
||||||
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
||||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatBarCacheTest::replyMissingUser()
|
|
||||||
{
|
|
||||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
|
||||||
chatBarCache->setText(u"some text"_s);
|
|
||||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
|
||||||
chatBarCache->setReplyId(u"$153456789:example.org"_s);
|
|
||||||
|
|
||||||
QCOMPARE(chatBarCache->text(), u"some text"_s);
|
|
||||||
QCOMPARE(chatBarCache->isReplying(), true);
|
|
||||||
QCOMPARE(chatBarCache->replyId(), u"$153456789:example.org"_s);
|
|
||||||
QCOMPARE(chatBarCache->isEditing(), false);
|
|
||||||
QCOMPARE(chatBarCache->editId(), QString());
|
|
||||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
|
||||||
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
|
||||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
|
||||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
|
|
||||||
|
|
||||||
QSignalSpy relationAuthorIsPresentSpy(chatBarCache.get(), &ChatBarCache::relationAuthorIsPresentChanged);
|
|
||||||
|
|
||||||
// sync again, which will simulate the reply user leaving the room
|
|
||||||
room->syncNewEvents(u"test-min-sync-extra-sync.json"_s);
|
|
||||||
|
|
||||||
QTRY_COMPARE(relationAuthorIsPresentSpy.count(), 1);
|
|
||||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatBarCacheTest::edit()
|
void ChatBarCacheTest::edit()
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"state": {
|
|
||||||
"events": [
|
|
||||||
{
|
|
||||||
"content": {
|
|
||||||
"membership": "leave"
|
|
||||||
},
|
|
||||||
"event_id": "$1432735824666PhrSA:example.org",
|
|
||||||
"origin_server_ts": 1432735824666,
|
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
|
||||||
"sender": "@example:example.org",
|
|
||||||
"state_key": "@example:example.org",
|
|
||||||
"type": "m.room.member",
|
|
||||||
"unsigned": {
|
|
||||||
"replaces_state": "$143273582443PhrSn:example.org"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
#include <Quotient/roommember.h>
|
#include <Quotient/roommember.h>
|
||||||
#include <Quotient/syncdata.h>
|
#include <Quotient/syncdata.h>
|
||||||
|
|
||||||
#include "models/eventmessagecontentmodel.h"
|
#include "models/messagecontentmodel.h"
|
||||||
|
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
#include "testutils.h"
|
#include "testutils.h"
|
||||||
@@ -39,17 +39,17 @@ void MessageContentModelTest::initTestCase()
|
|||||||
void MessageContentModelTest::missingEvent()
|
void MessageContentModelTest::missingEvent()
|
||||||
{
|
{
|
||||||
auto room = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
|
auto room = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
|
||||||
auto model1 = EventMessageContentModel(room, u"$153456789:example.org"_s);
|
auto model1 = MessageContentModel(room, u"$153456789:example.org"_s);
|
||||||
|
|
||||||
QCOMPARE(model1.rowCount(), 1);
|
QCOMPARE(model1.rowCount(), 1);
|
||||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading…"_s);
|
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading"_s);
|
||||||
|
|
||||||
auto model2 = EventMessageContentModel(room, u"$153456789:example.org"_s, true);
|
auto model2 = MessageContentModel(room, u"$153456789:example.org"_s, true);
|
||||||
|
|
||||||
QCOMPARE(model2.rowCount(), 1);
|
QCOMPARE(model2.rowCount(), 1);
|
||||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply…"_s);
|
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply"_s);
|
||||||
|
|
||||||
room->syncNewEvents(u"test-min-sync.json"_s);
|
room->syncNewEvents(u"test-min-sync.json"_s);
|
||||||
QCOMPARE(model1.rowCount(), 2);
|
QCOMPARE(model1.rowCount(), 2);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#include <Quotient/events/roommessageevent.h>
|
#include <Quotient/events/roommessageevent.h>
|
||||||
|
|
||||||
#include "models/eventmessagecontentmodel.h"
|
#include "models/messagecontentmodel.h"
|
||||||
#include "testutils.h"
|
#include "testutils.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -21,7 +21,7 @@ class ReactionModelTest : public QObject
|
|||||||
private:
|
private:
|
||||||
Connection *connection = nullptr;
|
Connection *connection = nullptr;
|
||||||
TestUtils::TestRoom *room = nullptr;
|
TestUtils::TestRoom *room = nullptr;
|
||||||
EventMessageContentModel *parentModel;
|
MessageContentModel *parentModel;
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void initTestCase();
|
void initTestCase();
|
||||||
@@ -34,7 +34,7 @@ void ReactionModelTest::initTestCase()
|
|||||||
{
|
{
|
||||||
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
|
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
|
||||||
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s);
|
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s);
|
||||||
parentModel = new EventMessageContentModel(room, "123456"_L1);
|
parentModel = new MessageContentModel(room, "123456"_L1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReactionModelTest::basicReaction()
|
void ReactionModelTest::basicReaction()
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QSignalSpy>
|
|
||||||
#include <QTest>
|
|
||||||
#include <QVariantList>
|
|
||||||
|
|
||||||
#include "accountmanager.h"
|
|
||||||
#include "models/actionsmodel.h"
|
|
||||||
#include "roommanager.h"
|
|
||||||
|
|
||||||
#include "server.h"
|
|
||||||
#include "testutils.h"
|
|
||||||
|
|
||||||
using namespace Quotient;
|
|
||||||
|
|
||||||
class RoomManagerTest : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private:
|
|
||||||
NeoChatConnection *connection = nullptr;
|
|
||||||
NeoChatRoom *room = nullptr;
|
|
||||||
|
|
||||||
Server server;
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void initTestCase();
|
|
||||||
void testMaximizeMedia();
|
|
||||||
void testResolveMatrixLinks();
|
|
||||||
};
|
|
||||||
|
|
||||||
void RoomManagerTest::initTestCase()
|
|
||||||
{
|
|
||||||
Connection::setRoomType<NeoChatRoom>();
|
|
||||||
server.start();
|
|
||||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
|
||||||
auto accountManager = new AccountManager(true);
|
|
||||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
|
||||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
|
||||||
QVERIFY(connection);
|
|
||||||
auto roomId = server.createRoom(u"@user:localhost:1234"_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);
|
|
||||||
RoomManager::instance().setConnection(connection);
|
|
||||||
QSignalSpy roomSpy(&RoomManager::instance(), &RoomManager::currentRoomChanged);
|
|
||||||
RoomManager::instance().resolveResource(room->id());
|
|
||||||
QVERIFY(roomSpy.size() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomManagerTest::testMaximizeMedia()
|
|
||||||
{
|
|
||||||
QSignalSpy spy(&RoomManager::instance(), &RoomManager::showMaximizedMedia);
|
|
||||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Tried to open media for empty event id");
|
|
||||||
RoomManager::instance().maximizeMedia(QString());
|
|
||||||
QVERIFY(!spy.wait(10));
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Tried to open media for unknown event id \"Doesn't exist\"");
|
|
||||||
RoomManager::instance().maximizeMedia(u"Doesn't exist"_s);
|
|
||||||
QVERIFY(!spy.wait(10));
|
|
||||||
|
|
||||||
const auto eventWithoutMedia = server.sendEvent(room->id(),
|
|
||||||
u"m.room.message"_s,
|
|
||||||
QJsonObject({
|
|
||||||
{u"body"_s, u"Foo"_s},
|
|
||||||
{u"format"_s, u"org.matrix.custom.html"_s},
|
|
||||||
{u"formatted_body"_s, u"Foo"_s},
|
|
||||||
{u"msgtype"_s, u"m.text"_s},
|
|
||||||
}));
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, u"Tried to open media for unknown event id \"%1\""_s.arg(eventWithoutMedia).toLatin1().data());
|
|
||||||
RoomManager::instance().maximizeMedia(eventWithoutMedia);
|
|
||||||
QVERIFY(!spy.wait(10));
|
|
||||||
|
|
||||||
// NOTE: This is supposed to test that maximizing pending media works correctly. This probably doesn't work in the UI yet, but at least the backend supports
|
|
||||||
// it. If the server ever learns how to process events, this becomes pointless and we need to find a way of preventing *these* events from arriving
|
|
||||||
auto pendingEventWithoutMedia = room->postText(u"Hello"_s);
|
|
||||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, u"Tried to open media for unknown event id \"%1\""_s.arg(pendingEventWithoutMedia).toLatin1().data());
|
|
||||||
RoomManager::instance().maximizeMedia(pendingEventWithoutMedia);
|
|
||||||
QVERIFY(!spy.wait(10));
|
|
||||||
|
|
||||||
const auto eventWithMedia = server.sendEvent(room->id(),
|
|
||||||
u"m.room.message"_s,
|
|
||||||
QJsonObject({
|
|
||||||
{u"body"_s, u"Foo"_s},
|
|
||||||
{u"filename"_s, u"foo.jpg"_s},
|
|
||||||
{u"info"_s,
|
|
||||||
QJsonObject{
|
|
||||||
{u"h"_s, 1000},
|
|
||||||
{u"w"_s, 2000},
|
|
||||||
{u"size"_s, 10000},
|
|
||||||
{u"mimetype"_s, u"image/png"_s},
|
|
||||||
}},
|
|
||||||
{u"msgtype"_s, u"m.image"_s},
|
|
||||||
{u"url"_s, u"mxc://foo.bar/asdf"_s},
|
|
||||||
}));
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
RoomManager::instance().maximizeMedia(eventWithMedia);
|
|
||||||
QVERIFY(spy.size() == 1);
|
|
||||||
QVERIFY(spy[0][0] == 0);
|
|
||||||
|
|
||||||
auto pendingEventWithMedia = room->postJson(u"m.room.message"_s,
|
|
||||||
QJsonObject({
|
|
||||||
{u"body"_s, u"Foo"_s},
|
|
||||||
{u"filename"_s, u"foo.jpg"_s},
|
|
||||||
{u"info"_s,
|
|
||||||
QJsonObject{
|
|
||||||
{u"h"_s, 1000},
|
|
||||||
{u"w"_s, 2000},
|
|
||||||
{u"size"_s, 10000},
|
|
||||||
{u"mimetype"_s, u"image/png"_s},
|
|
||||||
}},
|
|
||||||
{u"msgtype"_s, u"m.image"_s},
|
|
||||||
{u"url"_s, u"mxc://foo.bar/asdf"_s},
|
|
||||||
}));
|
|
||||||
RoomManager::instance().maximizeMedia(pendingEventWithMedia);
|
|
||||||
QVERIFY(spy.size() == 2);
|
|
||||||
QVERIFY(spy[1][0] == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RoomManagerTest::testResolveMatrixLinks()
|
|
||||||
{
|
|
||||||
// Test if resolving a non-joined room will bring up the confirmation dialog.
|
|
||||||
const QSignalSpy askToJoinSpy(&RoomManager::instance(), &RoomManager::askJoinRoom);
|
|
||||||
RoomManager::instance().resolveResource(QStringLiteral("matrix:r/testbuild:matrix.org"), QStringLiteral("join"));
|
|
||||||
QTRY_COMPARE(askToJoinSpy.size(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
QTEST_MAIN(RoomManagerTest)
|
|
||||||
#include "roommanagertest.moc"
|
|
||||||
@@ -109,20 +109,98 @@ void Server::start()
|
|||||||
m_server.route(u"/_matrix/client/v3/rooms/<arg>/invite"_s,
|
m_server.route(u"/_matrix/client/v3/rooms/<arg>/invite"_s,
|
||||||
QHttpServerRequest::Method::Post,
|
QHttpServerRequest::Method::Post,
|
||||||
[this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) {
|
[this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) {
|
||||||
Changes changes;
|
m_invitedUsers[roomId] += QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString();
|
||||||
changes.invitations += Changes::InviteUser{
|
|
||||||
.userId = QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString(),
|
|
||||||
.roomId = roomId,
|
|
||||||
};
|
|
||||||
m_state += changes;
|
|
||||||
responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok);
|
responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok);
|
||||||
});
|
});
|
||||||
|
|
||||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, this, &Server::sync);
|
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;
|
QSslConfiguration config;
|
||||||
QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s);
|
QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s);
|
||||||
void(key.open(QFile::ReadOnly));
|
key.open(QFile::ReadOnly);
|
||||||
config.setPrivateKey(QSslKey(&key, QSsl::Rsa));
|
config.setPrivateKey(QSslKey(&key, QSsl::Rsa));
|
||||||
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front());
|
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front());
|
||||||
m_sslServer.setSslConfiguration(config);
|
m_sslServer.setSslConfiguration(config);
|
||||||
@@ -136,239 +214,22 @@ void Server::start()
|
|||||||
|
|
||||||
QString Server::createRoom(const QString &matrixId)
|
QString Server::createRoom(const QString &matrixId)
|
||||||
{
|
{
|
||||||
const auto roomId = generateRoomId();
|
auto roomId = generateRoomId();
|
||||||
Changes changes;
|
m_roomsToCreate += {roomId, matrixId};
|
||||||
changes.newRooms += Changes::NewRoom{
|
|
||||||
.initialMembers = {matrixId},
|
|
||||||
.roomId = {roomId},
|
|
||||||
.tags = {},
|
|
||||||
};
|
|
||||||
m_state += changes;
|
|
||||||
return roomId;
|
return roomId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::inviteUser(const QString &roomId, const QString &matrixId)
|
void Server::inviteUser(const QString &roomId, const QString &matrixId)
|
||||||
{
|
{
|
||||||
Changes changes;
|
m_invitedUsers[roomId] += matrixId;
|
||||||
changes.invitations += Changes::InviteUser{
|
|
||||||
.userId = matrixId,
|
|
||||||
.roomId = roomId,
|
|
||||||
};
|
|
||||||
m_state += changes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::banUser(const QString &roomId, const QString &matrixId)
|
void Server::banUser(const QString &roomId, const QString &matrixId)
|
||||||
{
|
{
|
||||||
Changes changes;
|
m_bannedUsers[roomId] += matrixId;
|
||||||
changes.bans += Changes::BanUser{
|
|
||||||
.userId = matrixId,
|
|
||||||
.roomId = roomId,
|
|
||||||
};
|
|
||||||
m_state += changes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::joinUser(const QString &roomId, const QString &matrixId)
|
void Server::joinUser(const QString &roomId, const QString &matrixId)
|
||||||
{
|
{
|
||||||
Changes changes;
|
m_joinedUsers[roomId] += matrixId;
|
||||||
changes.joins += Changes::JoinUser{
|
|
||||||
.userId = matrixId,
|
|
||||||
.roomId = roomId,
|
|
||||||
};
|
|
||||||
m_state += changes;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Server::createServerNoticesRoom(const QString &matrixId)
|
|
||||||
{
|
|
||||||
const auto roomId = generateRoomId();
|
|
||||||
Changes changes;
|
|
||||||
changes.newRooms += Changes::NewRoom{
|
|
||||||
.initialMembers = {matrixId},
|
|
||||||
.roomId = {roomId},
|
|
||||||
.tags = {u"m.server_notice"_s},
|
|
||||||
};
|
|
||||||
m_state += changes;
|
|
||||||
return roomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString Server::sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content)
|
|
||||||
{
|
|
||||||
Changes changes;
|
|
||||||
const auto eventId = generateEventId();
|
|
||||||
changes.events += Changes::Event{
|
|
||||||
.fullJson = QJsonObject{{u"type"_s, eventType},
|
|
||||||
{u"content"_s, content},
|
|
||||||
{u"sender"_s, u"@foo:server.com"_s},
|
|
||||||
{u"event_id"_s, eventId},
|
|
||||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
|
||||||
{u"room_id"_s, roomId}},
|
|
||||||
};
|
|
||||||
m_state += changes;
|
|
||||||
return eventId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &responder)
|
|
||||||
{
|
|
||||||
QJsonObject joinRooms;
|
|
||||||
auto token = request.query().queryItemValue(u"since"_s).toInt();
|
|
||||||
|
|
||||||
for (const auto &change : m_state.mid(token)) {
|
|
||||||
for (const auto &newRoom : change.newRooms) {
|
|
||||||
QJsonArray stateEvents;
|
|
||||||
stateEvents += 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, newRoom.roomId},
|
|
||||||
{u"sender"_s, newRoom.initialMembers[0]},
|
|
||||||
{u"state_key"_s, QString()},
|
|
||||||
{u"type"_s, u"m.room.create"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
for (const auto &member : newRoom.initialMembers) {
|
|
||||||
stateEvents += 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, newRoom.roomId},
|
|
||||||
{u"sender"_s, member},
|
|
||||||
{u"state_key"_s, member},
|
|
||||||
{u"type"_s, u"m.room.member"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto room = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents}}}};
|
|
||||||
|
|
||||||
QJsonArray roomAccountData;
|
|
||||||
QJsonObject tags;
|
|
||||||
for (const auto &tag : newRoom.tags) {
|
|
||||||
tags[tag] = QJsonObject();
|
|
||||||
}
|
|
||||||
if (!tags.empty()) {
|
|
||||||
roomAccountData += QJsonObject{{u"type"_s, u"m.tag"_s}, {u"content"_s, QJsonObject{{u"tags"_s, tags}}}};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (roomAccountData.size() > 0) {
|
|
||||||
room[u"account_data"] = QJsonObject{{u"events"_s, roomAccountData}};
|
|
||||||
}
|
|
||||||
|
|
||||||
joinRooms[newRoom.roomId] = room;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &change : m_state.mid(token)) {
|
|
||||||
for (const auto &invitation : change.invitations) {
|
|
||||||
// TODO: The invitation could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
|
||||||
auto stateEvents = joinRooms[invitation.roomId][u"state"_s][u"events"_s].toArray();
|
|
||||||
stateEvents += 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, invitation.roomId},
|
|
||||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
|
||||||
{u"state_key"_s, invitation.userId},
|
|
||||||
{u"type"_s, u"m.room.member"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
if (joinRooms.contains(invitation.roomId)) {
|
|
||||||
auto room = joinRooms[invitation.roomId].toObject();
|
|
||||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
|
||||||
joinRooms[invitation.roomId] = room;
|
|
||||||
} else {
|
|
||||||
joinRooms[invitation.roomId] = QJsonObject{{u"state"_s,
|
|
||||||
QJsonObject{
|
|
||||||
{u"events"_s, stateEvents},
|
|
||||||
}}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &change : m_state.mid(token)) {
|
|
||||||
for (const auto &ban : change.bans) {
|
|
||||||
// TODO: The ban could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
|
||||||
auto stateEvents = joinRooms[ban.roomId][u"state"_s][u"events"_s].toArray();
|
|
||||||
stateEvents += 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, ban.roomId},
|
|
||||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
|
||||||
{u"state_key"_s, ban.userId},
|
|
||||||
{u"type"_s, u"m.room.member"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
if (joinRooms.contains(ban.roomId)) {
|
|
||||||
auto room = joinRooms[ban.roomId].toObject();
|
|
||||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
|
||||||
joinRooms[ban.roomId] = room;
|
|
||||||
} else {
|
|
||||||
joinRooms[ban.roomId] = QJsonObject{{u"state"_s,
|
|
||||||
QJsonObject{
|
|
||||||
{u"events"_s, stateEvents},
|
|
||||||
}}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &change : m_state.mid(token)) {
|
|
||||||
for (const auto &join : change.joins) {
|
|
||||||
// TODO: The join could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
|
||||||
auto stateEvents = joinRooms[join.roomId][u"state"_s][u"events"_s].toArray();
|
|
||||||
stateEvents += 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, join.roomId},
|
|
||||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
|
||||||
{u"state_key"_s, join.userId},
|
|
||||||
{u"type"_s, u"m.room.member"_s},
|
|
||||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
|
||||||
};
|
|
||||||
if (joinRooms.contains(join.roomId)) {
|
|
||||||
auto room = joinRooms[join.roomId].toObject();
|
|
||||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
|
||||||
joinRooms[join.roomId] = room;
|
|
||||||
} else {
|
|
||||||
joinRooms[join.roomId] = QJsonObject{{u"state"_s,
|
|
||||||
QJsonObject{
|
|
||||||
{u"events"_s, stateEvents},
|
|
||||||
}}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &change : m_state.mid(token)) {
|
|
||||||
for (const auto &event : change.events) {
|
|
||||||
// TODO the room might be in a different join state.
|
|
||||||
auto timeline = joinRooms[event.fullJson[u"room_id"_s].toString()][u"timeline"_s][u"events"_s].toArray();
|
|
||||||
timeline += event.fullJson;
|
|
||||||
if (joinRooms.contains(event.fullJson[u"room_id"_s].toString())) {
|
|
||||||
auto room = joinRooms[event.fullJson[u"room_id"_s].toString()].toObject();
|
|
||||||
room[u"timeline"_s] = QJsonObject{{u"events"_s, timeline}};
|
|
||||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = room;
|
|
||||||
} else {
|
|
||||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = QJsonObject{
|
|
||||||
{u"timeline"_s, QJsonObject{{u"events"_s, timeline}}},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject syncData = {
|
|
||||||
// {u"account_data"_s, QJsonObject {}},
|
|
||||||
// {u"presence"_s, QJsonObject {}},
|
|
||||||
{u"next_batch"_s, QString::number(m_state.size())},
|
|
||||||
};
|
|
||||||
|
|
||||||
QJsonObject rooms;
|
|
||||||
if (!joinRooms.isEmpty()) {
|
|
||||||
rooms[u"join"_s] = joinRooms;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rooms.empty()) {
|
|
||||||
syncData[u"rooms"_s] = rooms;
|
|
||||||
}
|
|
||||||
|
|
||||||
qWarning() << syncData;
|
|
||||||
responder.write(QJsonDocument(syncData), QHttpServerResponder::StatusCode::Ok);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,51 +2,10 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
#include <QHttpServer>
|
#include <QHttpServer>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QSslServer>
|
#include <QSslServer>
|
||||||
|
|
||||||
struct Changes {
|
class Server
|
||||||
struct NewRoom {
|
|
||||||
QStringList initialMembers;
|
|
||||||
QString roomId;
|
|
||||||
QStringList tags;
|
|
||||||
};
|
|
||||||
QList<NewRoom> newRooms;
|
|
||||||
|
|
||||||
struct InviteUser {
|
|
||||||
QString userId;
|
|
||||||
QString roomId;
|
|
||||||
};
|
|
||||||
QList<InviteUser> invitations;
|
|
||||||
|
|
||||||
struct BanUser {
|
|
||||||
QString userId;
|
|
||||||
QString roomId;
|
|
||||||
};
|
|
||||||
QList<BanUser> bans;
|
|
||||||
|
|
||||||
struct JoinUser {
|
|
||||||
QString userId;
|
|
||||||
QString roomId;
|
|
||||||
};
|
|
||||||
QList<JoinUser> joins;
|
|
||||||
|
|
||||||
struct Event {
|
|
||||||
QJsonObject fullJson;
|
|
||||||
};
|
|
||||||
QList<Event> events;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RoomData {
|
|
||||||
QStringList members;
|
|
||||||
QString id;
|
|
||||||
QStringList tags;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Server : public QObject
|
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Server();
|
Server();
|
||||||
|
|
||||||
@@ -62,17 +21,13 @@ public:
|
|||||||
void banUser(const QString &roomId, const QString &matrixId);
|
void banUser(const QString &roomId, const QString &matrixId);
|
||||||
void joinUser(const QString &roomId, const QString &matrixId);
|
void joinUser(const QString &roomId, const QString &matrixId);
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a server notices room.
|
|
||||||
*/
|
|
||||||
QString createServerNoticesRoom(const QString &matrixId);
|
|
||||||
QString sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHttpServer m_server;
|
QHttpServer m_server;
|
||||||
QSslServer m_sslServer;
|
QSslServer m_sslServer;
|
||||||
|
|
||||||
void sync(const QHttpServerRequest &request, QHttpServerResponder &responder);
|
QHash<QString, QList<QString>> m_invitedUsers;
|
||||||
|
QHash<QString, QList<QString>> m_bannedUsers;
|
||||||
|
QHash<QString, QList<QString>> m_joinedUsers;
|
||||||
|
|
||||||
QList<Changes> m_state;
|
QList<std::pair<QString, QString>> m_roomsToCreate;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QSignalSpy>
|
|
||||||
#include <QTest>
|
|
||||||
|
|
||||||
#include <KLocalizedString>
|
|
||||||
|
|
||||||
#include <Quotient/connection.h>
|
|
||||||
#include <Quotient/eventstats.h>
|
|
||||||
#include <Quotient/quotient_common.h>
|
|
||||||
#include <Quotient/syncdata.h>
|
|
||||||
|
|
||||||
#include "accountmanager.h"
|
|
||||||
#include "neochatroom.h"
|
|
||||||
#include "roommanager.h"
|
|
||||||
#include "server.h"
|
|
||||||
|
|
||||||
#include "testutils.h"
|
|
||||||
|
|
||||||
using namespace Quotient;
|
|
||||||
|
|
||||||
class ServerNoticesTest : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
private:
|
|
||||||
NeoChatConnection *connection = nullptr;
|
|
||||||
Server server;
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void initTestCase();
|
|
||||||
void test();
|
|
||||||
};
|
|
||||||
|
|
||||||
void ServerNoticesTest::initTestCase()
|
|
||||||
{
|
|
||||||
Connection::setRoomType<NeoChatRoom>();
|
|
||||||
server.start();
|
|
||||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
|
||||||
auto accountManager = new AccountManager(true);
|
|
||||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
|
||||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
|
||||||
QVERIFY(connection);
|
|
||||||
auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
|
||||||
RoomManager::instance().setConnection(connection);
|
|
||||||
|
|
||||||
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());
|
|
||||||
auto room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
|
||||||
QVERIFY(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ServerNoticesTest::test()
|
|
||||||
{
|
|
||||||
auto roomTreeModel = RoomManager::instance().roomTreeModel();
|
|
||||||
QCOMPARE(roomTreeModel->rowCount(roomTreeModel->index(NeoChatRoomType::ServerNotice, 0)), 0);
|
|
||||||
auto sortFilterRoomTreeModel = RoomManager::instance().sortFilterRoomTreeModel();
|
|
||||||
const auto roomId = server.createServerNoticesRoom(u"@user:localhost:1234"_s);
|
|
||||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
const auto room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
|
||||||
QVERIFY(connection->room(roomId)->isServerNoticeRoom());
|
|
||||||
QCOMPARE(roomTreeModel->rowCount(roomTreeModel->index(NeoChatRoomType::ServerNotice, 0)), 1);
|
|
||||||
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 1 /* Below the normal room */);
|
|
||||||
server.sendEvent(roomId,
|
|
||||||
u"m.room.message"_s,
|
|
||||||
QJsonObject{
|
|
||||||
{u"body"_s, u"Foo"_s},
|
|
||||||
{u"format"_s, u"org.matrix.custom.html"_s},
|
|
||||||
{u"formatted_body"_s, u"Foo"_s},
|
|
||||||
{u"msgtype"_s, u"m.text"_s},
|
|
||||||
});
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
QVERIFY(syncSpy.wait());
|
|
||||||
sortFilterRoomTreeModel->invalidate();
|
|
||||||
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 0);
|
|
||||||
room->markAllMessagesAsRead();
|
|
||||||
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 1 /* Below the normal room */);
|
|
||||||
}
|
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(ServerNoticesTest)
|
|
||||||
#include "servernoticestest.moc"
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
#include <QTest>
|
|
||||||
#include <Quotient/events/event.h>
|
#include <Quotient/events/event.h>
|
||||||
#include <Quotient/syncdata.h>
|
#include <Quotient/syncdata.h>
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ public:
|
|||||||
if (!syncFileName.isEmpty()) {
|
if (!syncFileName.isEmpty()) {
|
||||||
QFile testSyncFile;
|
QFile testSyncFile;
|
||||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||||
Q_UNUSED(testSyncFile.open(QIODevice::ReadOnly));
|
testSyncFile.open(QIODevice::ReadOnly);
|
||||||
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||||
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
|
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
|
||||||
update(std::move(roomData));
|
update(std::move(roomData));
|
||||||
@@ -47,7 +46,7 @@ inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFile
|
|||||||
if (!eventFileName.isEmpty()) {
|
if (!eventFileName.isEmpty()) {
|
||||||
QFile testEventFile;
|
QFile testEventFile;
|
||||||
testEventFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + eventFileName);
|
testEventFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + eventFileName);
|
||||||
Q_UNUSED(testEventFile.open(QIODevice::ReadOnly));
|
testEventFile.open(QIODevice::ReadOnly);
|
||||||
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
|
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
|
||||||
return Quotient::loadEvent<EventT>(testSyncJson);
|
return Quotient::loadEvent<EventT>(testSyncJson);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,6 @@ private Q_SLOTS:
|
|||||||
void stripDisallowedTags();
|
void stripDisallowedTags();
|
||||||
void stripDisallowedAttributes();
|
void stripDisallowedAttributes();
|
||||||
void emptyCodeTags();
|
void emptyCodeTags();
|
||||||
void addStyle_data();
|
|
||||||
void addStyle();
|
|
||||||
void dontAddStyle_data();
|
|
||||||
void dontAddStyle();
|
|
||||||
|
|
||||||
void sendSimpleStringCase();
|
void sendSimpleStringCase();
|
||||||
void sendSingleParaMarkup();
|
void sendSingleParaMarkup();
|
||||||
@@ -75,9 +71,6 @@ private Q_SLOTS:
|
|||||||
|
|
||||||
void componentOutput_data();
|
void componentOutput_data();
|
||||||
void componentOutput();
|
void componentOutput();
|
||||||
|
|
||||||
void updateSpoiler_data();
|
|
||||||
void updateSpoiler();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void TextHandlerTest::initTestCase()
|
void TextHandlerTest::initTestCase()
|
||||||
@@ -96,26 +89,21 @@ void TextHandlerTest::initTestCase()
|
|||||||
|
|
||||||
void TextHandlerTest::allowedAttributes()
|
void TextHandlerTest::allowedAttributes()
|
||||||
{
|
{
|
||||||
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
|
||||||
const QString testInputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
const QString testInputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||||
const QString testOutputString1S = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
const QString testOutputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||||
const QString testOutputString1R = u"<span data-mx-spoiler style=\"color: transparent; background: %1;\"><font color=#FFFFFF>Test</font><span>"_s.arg(
|
|
||||||
theme->alternateBackgroundColor().name());
|
|
||||||
// Handle urls where the href has either single (') or double (") quotes.
|
// Handle urls where the href has either single (') or double (") quotes.
|
||||||
const QString testInputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
const QString testInputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
||||||
const QString testOutputString2S = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
const QString testOutputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
||||||
const QString testOutputString2R =
|
|
||||||
u"<a href=\"https://kde.org\" style=\"text-decoration: none;\">link</a><a href='https://kde.org' style=\"text-decoration: none;\">link</a>"_s;
|
|
||||||
|
|
||||||
TextHandler testTextHandler;
|
TextHandler testTextHandler;
|
||||||
testTextHandler.setData(testInputString1);
|
testTextHandler.setData(testInputString1);
|
||||||
|
|
||||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString1S);
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString1);
|
||||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1R);
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1);
|
||||||
|
|
||||||
testTextHandler.setData(testInputString2);
|
testTextHandler.setData(testInputString2);
|
||||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString2S);
|
QCOMPARE(testTextHandler.handleSendText(), testOutputString2);
|
||||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2R);
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextHandlerTest::stripDisallowedTags()
|
void TextHandlerTest::stripDisallowedTags()
|
||||||
@@ -158,56 +146,6 @@ void TextHandlerTest::emptyCodeTags()
|
|||||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextHandlerTest::addStyle_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QString>("testInputString");
|
|
||||||
QTest::addColumn<QString>("testOutputString");
|
|
||||||
|
|
||||||
QTest::newRow("link") << u"<a href=\"https://kde.org\">link</a>"_s << u"<a href=\"https://kde.org\" style=\"text-decoration: none;\">link</a>"_s;
|
|
||||||
QTest::newRow("table")
|
|
||||||
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s
|
|
||||||
<< u"<table style=\"width: 100%; border-collapse: collapse; border: 1px; border-style: solid;\"><tr><th style=\"border: 1px solid black; padding: 3px;\">Company</th><th style=\"border: 1px solid black; padding: 3px;\">Contact</th><th style=\"border: 1px solid black; padding: 3px;\">Country</th></tr><tr><td style=\"border: 1px solid black; padding: 3px;\">Alfreds Futterkiste</td><td style=\"border: 1px solid black; padding: 3px;\">Maria Anders</td><td style=\"border: 1px solid black; padding: 3px;\">Germany</td></tr><tr><td style=\"border: 1px solid black; padding: 3px;\">Centro comercial Moctezuma</td><td style=\"border: 1px solid black; padding: 3px;\">Francisco Chang</td><td style=\"border: 1px solid black; padding: 3px;\">Mexico</td></tr></table>"_s;
|
|
||||||
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
|
||||||
QTest::newRow("spoiler") << u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s
|
|
||||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\"><font color=#FFFFFF>Test</font><span>"_s.arg(
|
|
||||||
theme->alternateBackgroundColor().name());
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextHandlerTest::addStyle()
|
|
||||||
{
|
|
||||||
QFETCH(QString, testInputString);
|
|
||||||
QFETCH(QString, testOutputString);
|
|
||||||
|
|
||||||
TextHandler testTextHandler;
|
|
||||||
testTextHandler.setData(testInputString);
|
|
||||||
|
|
||||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextHandlerTest::dontAddStyle_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QString>("testInputString");
|
|
||||||
QTest::addColumn<QString>("testOutputString");
|
|
||||||
|
|
||||||
QTest::newRow("link") << u"<a href=\"https://kde.org\">link</a>"_s << u"<a href=\"https://kde.org\">link</a>"_s;
|
|
||||||
QTest::newRow("table")
|
|
||||||
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s
|
|
||||||
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s;
|
|
||||||
QTest::newRow("spoiler") << u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s
|
|
||||||
<< u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextHandlerTest::dontAddStyle()
|
|
||||||
{
|
|
||||||
QFETCH(QString, testInputString);
|
|
||||||
QFETCH(QString, testOutputString);
|
|
||||||
|
|
||||||
TextHandler testTextHandler;
|
|
||||||
testTextHandler.setData(testInputString);
|
|
||||||
|
|
||||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextHandlerTest::sendSimpleStringCase()
|
void TextHandlerTest::sendSimpleStringCase()
|
||||||
{
|
{
|
||||||
const QString testInputString = u"This data should just be left alone."_s;
|
const QString testInputString = u"This data should just be left alone."_s;
|
||||||
@@ -400,8 +338,7 @@ void TextHandlerTest::receiveRichInPlainOut()
|
|||||||
void TextHandlerTest::receivePlainTextIn()
|
void TextHandlerTest::receivePlainTextIn()
|
||||||
{
|
{
|
||||||
const QString testInputString = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
|
const QString testInputString = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
|
||||||
const QString testOutputStringRich =
|
const QString testOutputStringRich = u"<plain text in tag bracket><br>Test link <a href=\"https://kde.org\">https://kde.org</a>."_s;
|
||||||
u"<plain text in tag bracket><br>Test link <a href=\"https://kde.org\" style=\"text-decoration: none;\">https://kde.org</a>."_s;
|
|
||||||
QString testOutputStringPlain = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
|
QString testOutputStringPlain = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
|
||||||
|
|
||||||
// Make sure quotes are maintained in a plain string.
|
// Make sure quotes are maintained in a plain string.
|
||||||
@@ -471,7 +408,7 @@ void TextHandlerTest::receivePlainStripMarkup()
|
|||||||
void TextHandlerTest::receiveRichUserPill()
|
void TextHandlerTest::receiveRichUserPill()
|
||||||
{
|
{
|
||||||
const QString testInputString = u"<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>"_s;
|
const QString testInputString = u"<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>"_s;
|
||||||
const QString testOutputString = u"<b><a href=\"https://matrix.to/#/@alice:example.org\" style=\"text-decoration: none;\">@alice:example.org</a></b>"_s;
|
const QString testOutputString = u"<b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b>"_s;
|
||||||
|
|
||||||
TextHandler testTextHandler;
|
TextHandler testTextHandler;
|
||||||
testTextHandler.setData(testInputString);
|
testTextHandler.setData(testInputString);
|
||||||
@@ -523,23 +460,21 @@ void TextHandlerTest::receiveRichPlainUrl_data()
|
|||||||
// so we can confirm consistent behaviour for complex urls.
|
// so we can confirm consistent behaviour for complex urls.
|
||||||
QTest::addRow("link 1")
|
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"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\" style=\"text-decoration: none;\">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\" style=\"text-decoration: none;\">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
|
// 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
|
// contains what looks like and email. It was broken into 3 but needs to
|
||||||
// be just single link.
|
// be just single link.
|
||||||
QTest::addRow("link 2")
|
QTest::addRow("link 2")
|
||||||
<< u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s
|
<< 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/\" style=\"text-decoration: none;\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_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")
|
QTest::addRow("email") << uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
|
||||||
<< 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;
|
||||||
<< uR"(<a href="mailto:email@example.com" style="text-decoration: none;">email@example.com</a> <a href="mailto:email@example.com" style="text-decoration: none;">Link already rich</a>)"_s;
|
|
||||||
QTest::addRow("mxid")
|
QTest::addRow("mxid")
|
||||||
<< u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s
|
<< 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\" style=\"text-decoration: none;\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">Link already rich</a></b>"_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
|
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;
|
||||||
<< u"a <b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">@user:kde.org</a></b> b"_s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -661,35 +596,5 @@ void TextHandlerTest::componentOutput()
|
|||||||
QCOMPARE(testTextHandler.textComponents(testInputString), testOutputComponents);
|
QCOMPARE(testTextHandler.textComponents(testInputString), testOutputComponents);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextHandlerTest::updateSpoiler_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QString>("testInputString");
|
|
||||||
QTest::addColumn<QString>("testOutputString");
|
|
||||||
QTest::addColumn<bool>("spoilerRevealed");
|
|
||||||
|
|
||||||
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
|
||||||
QTest::newRow("same length") << u"<span data-mx-spoiler style=\"color: #123456; background: #123456;\">Test<span>"_s
|
|
||||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(
|
|
||||||
theme->alternateBackgroundColor().name())
|
|
||||||
<< false;
|
|
||||||
QTest::newRow("different length") << u"<span data-mx-spoiler style=\"color: short; background: looooooooooong;\">Test<span>"_s
|
|
||||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(
|
|
||||||
theme->alternateBackgroundColor().name())
|
|
||||||
<< false;
|
|
||||||
QTest::newRow("spoiler revealed")
|
|
||||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(theme->alternateBackgroundColor().name())
|
|
||||||
<< u"<span data-mx-spoiler style=\"color: %1; background: %2;\">Test<span>"_s.arg(theme->textColor().name(), theme->alternateBackgroundColor().name())
|
|
||||||
<< true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextHandlerTest::updateSpoiler()
|
|
||||||
{
|
|
||||||
QFETCH(QString, testInputString);
|
|
||||||
QFETCH(QString, testOutputString);
|
|
||||||
QFETCH(bool, spoilerRevealed);
|
|
||||||
|
|
||||||
QCOMPARE(TextHandler::updateSpoilerText(this, testInputString, spoilerRevealed), testOutputString);
|
|
||||||
}
|
|
||||||
|
|
||||||
QTEST_MAIN(TextHandlerTest)
|
QTEST_MAIN(TextHandlerTest)
|
||||||
#include "texthandlertest.moc"
|
#include "texthandlertest.moc"
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ void TimelineMessageModelTest::pendingEvent()
|
|||||||
// different every time.
|
// different every time.
|
||||||
QFile testSyncFile;
|
QFile testSyncFile;
|
||||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + u"test-pending-sync.json"_s);
|
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + u"test-pending-sync.json"_s);
|
||||||
QVERIFY(testSyncFile.open(QIODevice::ReadOnly));
|
testSyncFile.open(QIODevice::ReadOnly);
|
||||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||||
auto root = testSyncJson.object();
|
auto root = testSyncJson.object();
|
||||||
auto timeline = root["timeline"_L1].toObject();
|
auto timeline = root["timeline"_L1].toObject();
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
|
|
||||||
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
|
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||||
|
|
||||||
qt_add_executable(timeline_memtest
|
qt_add_executable(timeline-memtest
|
||||||
main.cpp
|
main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(timeline_memtest PRIVATE neochatplugin Timelineplugin)
|
target_link_libraries(timeline-memtest PRIVATE neochatplugin Timelineplugin)
|
||||||
target_link_libraries(timeline_memtest PUBLIC
|
target_link_libraries(timeline-memtest PUBLIC
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
Qt::Qml
|
Qt::Qml
|
||||||
@@ -16,13 +16,14 @@ target_link_libraries(timeline_memtest PUBLIC
|
|||||||
Qt::QuickControls2
|
Qt::QuickControls2
|
||||||
Qt::Widgets
|
Qt::Widgets
|
||||||
KF6::I18nQml
|
KF6::I18nQml
|
||||||
|
KF6::Kirigami
|
||||||
QuotientQt6
|
QuotientQt6
|
||||||
LibNeoChat
|
LibNeoChat
|
||||||
Timeline
|
Timeline
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_add_qml_module(timeline_memtest URI org.kde.neochat.timeline_memtest GENERATE_PLUGIN_SOURCE
|
ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE
|
||||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline_memtest
|
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline-memtest
|
||||||
QML_FILES
|
QML_FILES
|
||||||
Main.qml
|
Main.qml
|
||||||
SOURCES
|
SOURCES
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ int main(int argc, char **argv)
|
|||||||
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
|
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
|
||||||
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
|
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
|
||||||
|
|
||||||
engine.loadFromModule("org.kde.neochat.timeline_memtest", "Main");
|
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,7 @@ public:
|
|||||||
if (!syncFileName.isEmpty()) {
|
if (!syncFileName.isEmpty()) {
|
||||||
QFile testSyncFile;
|
QFile testSyncFile;
|
||||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||||
auto ok = testSyncFile.open(QIODevice::ReadOnly);
|
testSyncFile.open(QIODevice::ReadOnly);
|
||||||
if (!ok) {
|
|
||||||
qWarning() << "Failed to open" << testSyncFile.fileName() << testSyncFile.errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
|
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
|
||||||
auto timelineJson = testSyncJson["timeline"_L1].toObject();
|
auto timelineJson = testSyncJson["timeline"_L1].toObject();
|
||||||
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);
|
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<name xml:lang="pl">NeoChat</name>
|
<name xml:lang="pl">NeoChat</name>
|
||||||
<name xml:lang="pt">NeoChat</name>
|
<name xml:lang="pt">NeoChat</name>
|
||||||
<name xml:lang="pt-BR">NeoChat</name>
|
<name xml:lang="pt-BR">NeoChat</name>
|
||||||
|
<name xml:lang="ro">NeoChat</name>
|
||||||
<name xml:lang="ru">NeoChat</name>
|
<name xml:lang="ru">NeoChat</name>
|
||||||
<name xml:lang="sa">नवचैट्</name>
|
<name xml:lang="sa">नवचैट्</name>
|
||||||
<name xml:lang="sk">NeoChat</name>
|
<name xml:lang="sk">NeoChat</name>
|
||||||
@@ -75,6 +76,7 @@
|
|||||||
<summary xml:lang="nn">Prat med via Matrix</summary>
|
<summary xml:lang="nn">Prat med via Matrix</summary>
|
||||||
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
|
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
|
||||||
<summary xml:lang="pt-BR">Bate-papo na Matrix</summary>
|
<summary xml:lang="pt-BR">Bate-papo na Matrix</summary>
|
||||||
|
<summary xml:lang="ro">Discutați pe Matrix</summary>
|
||||||
<summary xml:lang="ru">Общение в Matrix</summary>
|
<summary xml:lang="ru">Общение в Matrix</summary>
|
||||||
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
|
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
|
||||||
<summary xml:lang="sl">Klepet na Matrixu</summary>
|
<summary xml:lang="sl">Klepet na Matrixu</summary>
|
||||||
@@ -109,6 +111,7 @@
|
|||||||
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
|
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
|
||||||
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
|
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
|
||||||
<p xml:lang="pt-BR">O NeoChat é um aplicativo de bate-papo que permite que você aproveite ao máximo a rede Matrix. Ele oferece uma maneira segura de enviar mensagens de texto, vídeos e arquivos de áudio para sua família, colegas e amigos.</p>
|
<p xml:lang="pt-BR">O NeoChat é um aplicativo de bate-papo que permite que você aproveite ao máximo a rede Matrix. Ele oferece uma maneira segura de enviar mensagens de texto, vídeos e arquivos de áudio para sua família, colegas e amigos.</p>
|
||||||
|
<p xml:lang="ro">NeoChat e o aplicație de discuții ce vă ajută să profitați din plin de rețeaua Matrix. Aceasta oferă o modalitate sigură de a trimite mesaje textuale, videoclipuri și fișiere audio familiei, colegilor și prietenilor.</p>
|
||||||
<p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p>
|
<p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p>
|
||||||
<p xml:lang="sa">NeoChat इति एकं गपशप-अनुप्रयोगं यत् भवान् Matrix-जालस्य पूर्णं लाभं ग्रहीतुं शक्नोति । एतत् भवन्तं भवतः परिवाराय, सहकारिभ्यः, मित्रेभ्यः च पाठसन्देशान्, भिडियो, श्रव्यसञ्चिकाः च प्रेषयितुं सुरक्षितं मार्गं प्रदाति ।</p>
|
<p xml:lang="sa">NeoChat इति एकं गपशप-अनुप्रयोगं यत् भवान् Matrix-जालस्य पूर्णं लाभं ग्रहीतुं शक्नोति । एतत् भवन्तं भवतः परिवाराय, सहकारिभ्यः, मित्रेभ्यः च पाठसन्देशान्, भिडियो, श्रव्यसञ्चिकाः च प्रेषयितुं सुरक्षितं मार्गं प्रदाति ।</p>
|
||||||
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
|
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
|
||||||
@@ -142,6 +145,7 @@
|
|||||||
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
|
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
|
||||||
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
|
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
|
||||||
<p xml:lang="pt-BR">O NeoChat pretende ser um aplicativo completo para a especificação Matrix. Dessa forma, tudo na especificação estável atual, com as notáveis exceções de VoIP, tópicos e alguns aspectos da criptografia de ponta a ponta, é suportado. Há algumas outras pequenas omissões devido ao fato de a especificação Matrix estar em constante evolução, mas o objetivo continua sendo fornecer suporte eventual para toda a especificação.</p>
|
<p xml:lang="pt-BR">O NeoChat pretende ser um aplicativo completo para a especificação Matrix. Dessa forma, tudo na especificação estável atual, com as notáveis exceções de VoIP, tópicos e alguns aspectos da criptografia de ponta a ponta, é suportado. Há algumas outras pequenas omissões devido ao fato de a especificação Matrix estar em constante evolução, mas o objetivo continua sendo fornecer suporte eventual para toda a especificação.</p>
|
||||||
|
<p xml:lang="ro">NeoChat vrea să fie o aplicație completă pentru specificațiile Matrix. Astfel, susține tot ce se găsește acum în specificațiile stabile cu excepția VoIP, a firelor de discuții, și a unor părți din criptarea punct-la-punct. Sunt și câteva omisiuni minore din cauza faptului că specificația Matrix evoluează continuu, dar scopul rămâne acela de a implementa întreaga specificație.</p>
|
||||||
<p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p>
|
<p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p>
|
||||||
<p xml:lang="sa">NeoChat इत्यस्य उद्देश्यं Matrix विनिर्देशस्य कृते पूर्णतया विशेषतायुक्तः अनुप्रयोगः भवितुम् अस्ति । यथा तथा वर्तमानस्थिरविनिर्देशे सर्वं VoIP इत्यस्य उल्लेखनीयअपवादैः सह, थ्रेड्स तथा च End-to-End Encryption इत्यस्य केचन पक्षाः समर्थिताः सन्ति । अन्ये कतिचन लघु लोपाः सन्ति यतोहि Matrix spec निरन्तरं विकसितः अस्ति परन्तु उद्देश्यं सम्पूर्ण spec कृते अन्ततः समर्थनं प्रदातुं अवशिष्टम् अस्ति</p>
|
<p xml:lang="sa">NeoChat इत्यस्य उद्देश्यं Matrix विनिर्देशस्य कृते पूर्णतया विशेषतायुक्तः अनुप्रयोगः भवितुम् अस्ति । यथा तथा वर्तमानस्थिरविनिर्देशे सर्वं VoIP इत्यस्य उल्लेखनीयअपवादैः सह, थ्रेड्स तथा च End-to-End Encryption इत्यस्य केचन पक्षाः समर्थिताः सन्ति । अन्ये कतिचन लघु लोपाः सन्ति यतोहि Matrix spec निरन्तरं विकसितः अस्ति परन्तु उद्देश्यं सम्पूर्ण spec कृते अन्ततः समर्थनं प्रदातुं अवशिष्टम् अस्ति</p>
|
||||||
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
|
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
|
||||||
@@ -175,6 +179,7 @@
|
|||||||
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
|
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
|
||||||
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
|
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
|
||||||
<p xml:lang="pt-BR">Devido à natureza do desenvolvimento da especificação Matrix, o NeoChat também suporta diversos recursos instáveis. Atualmente, são eles:</p>
|
<p xml:lang="pt-BR">Devido à natureza do desenvolvimento da especificação Matrix, o NeoChat também suporta diversos recursos instáveis. Atualmente, são eles:</p>
|
||||||
|
<p xml:lang="ro">Datorită modului de dezvoltare a specificațiilor Matrix, NeoChat susține și numeroase caracteristici nestabile. Acum, acestea sunt:</p>
|
||||||
<p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p>
|
<p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p>
|
||||||
<p xml:lang="sa">Matrix विनिर्देशविकासस्य प्रकृतेः कारणात् NeoChat अपि अनेकानाम् अस्थिरविशेषतानां समर्थनं करोति । सम्प्रति एते सन्ति :</p>
|
<p xml:lang="sa">Matrix विनिर्देशविकासस्य प्रकृतेः कारणात् NeoChat अपि अनेकानाम् अस्थिरविशेषतानां समर्थनं करोति । सम्प्रति एते सन्ति :</p>
|
||||||
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
|
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
|
||||||
@@ -209,6 +214,7 @@
|
|||||||
<li xml:lang="pl">Ankiety - MSC3381</li>
|
<li xml:lang="pl">Ankiety - MSC3381</li>
|
||||||
<li xml:lang="pt">Inquéritos - MSC3381</li>
|
<li xml:lang="pt">Inquéritos - MSC3381</li>
|
||||||
<li xml:lang="pt-BR">Enquetes - MSC3381</li>
|
<li xml:lang="pt-BR">Enquetes - MSC3381</li>
|
||||||
|
<li xml:lang="ro">Sondaje - MSC3381</li>
|
||||||
<li xml:lang="ru">Голосования — MSC3381</li>
|
<li xml:lang="ru">Голосования — MSC3381</li>
|
||||||
<li xml:lang="sa">मतदान - MSC3381</li>
|
<li xml:lang="sa">मतदान - MSC3381</li>
|
||||||
<li xml:lang="sl">Polls - MSC3381</li>
|
<li xml:lang="sl">Polls - MSC3381</li>
|
||||||
@@ -242,6 +248,7 @@
|
|||||||
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
|
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
|
||||||
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
|
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
|
||||||
<li xml:lang="pt-BR">Pacotes de Stickers - MSC2545</li>
|
<li xml:lang="pt-BR">Pacotes de Stickers - MSC2545</li>
|
||||||
|
<li xml:lang="ro">Colecții de abțibilduri - MSC2545</li>
|
||||||
<li xml:lang="ru">Наборы стикеров — MSC2545</li>
|
<li xml:lang="ru">Наборы стикеров — MSC2545</li>
|
||||||
<li xml:lang="sa">स्टिकर पैक - MSC2545</li>
|
<li xml:lang="sa">स्टिकर पैक - MSC2545</li>
|
||||||
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
||||||
@@ -275,6 +282,7 @@
|
|||||||
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
|
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
|
||||||
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
|
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
|
||||||
<li xml:lang="pt-BR">Localização de eventos - MSC3488</li>
|
<li xml:lang="pt-BR">Localização de eventos - MSC3488</li>
|
||||||
|
<li xml:lang="ro">Evenimente de amplasare - MSC3488</li>
|
||||||
<li xml:lang="ru">События местоположения — MSC3488</li>
|
<li xml:lang="ru">События местоположения — MSC3488</li>
|
||||||
<li xml:lang="sa">स्थान घटनाएँ - MSC3488</li>
|
<li xml:lang="sa">स्थान घटनाएँ - MSC3488</li>
|
||||||
<li xml:lang="sl">Location Events - MSC3488</li>
|
<li xml:lang="sl">Location Events - MSC3488</li>
|
||||||
@@ -298,8 +306,8 @@
|
|||||||
<keyword>Matrix</keyword>
|
<keyword>Matrix</keyword>
|
||||||
<keyword>Kirigami</keyword>
|
<keyword>Kirigami</keyword>
|
||||||
</keywords>
|
</keywords>
|
||||||
<developer id="org.kde">
|
<developer id="kde.org">
|
||||||
<name translate="no">KDE</name>
|
<name>The KDE Community</name>
|
||||||
<url>https://kde.org</url>
|
<url>https://kde.org</url>
|
||||||
</developer>
|
</developer>
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
@@ -344,6 +352,7 @@
|
|||||||
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
|
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
|
||||||
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
|
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
|
||||||
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
|
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
|
||||||
|
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
|
||||||
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
|
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
|
||||||
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
|
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
|
||||||
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
|
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
|
||||||
@@ -380,6 +389,7 @@
|
|||||||
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
|
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
|
||||||
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
|
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
|
||||||
<caption xml:lang="pt-BR">Descubra novas comunidades com os Espaços Matrix</caption>
|
<caption xml:lang="pt-BR">Descubra novas comunidades com os Espaços Matrix</caption>
|
||||||
|
<caption xml:lang="ro">Descoperiți comunități noi cu Spații Matrix</caption>
|
||||||
<caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption>
|
<caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption>
|
||||||
<caption xml:lang="sa">Matrix Spaces इत्यनेन सह नूतनानां समुदायानाम् अन्वेषणं कुर्वन्तु</caption>
|
<caption xml:lang="sa">Matrix Spaces इत्यनेन सह नूतनानां समुदायानाम् अन्वेषणं कुर्वन्तु</caption>
|
||||||
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
|
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
|
||||||
@@ -424,6 +434,7 @@
|
|||||||
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
|
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
|
||||||
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
|
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
|
||||||
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
|
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
|
||||||
|
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
|
||||||
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
|
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
|
||||||
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
|
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
|
||||||
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
|
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
|
||||||
@@ -462,6 +473,7 @@
|
|||||||
<caption xml:lang="pl">Ekran logowania</caption>
|
<caption xml:lang="pl">Ekran logowania</caption>
|
||||||
<caption xml:lang="pt">Ecrã de autenticação</caption>
|
<caption xml:lang="pt">Ecrã de autenticação</caption>
|
||||||
<caption xml:lang="pt-BR">Tela de login</caption>
|
<caption xml:lang="pt-BR">Tela de login</caption>
|
||||||
|
<caption xml:lang="ro">Ecran de autentificare</caption>
|
||||||
<caption xml:lang="ru">Окно входа</caption>
|
<caption xml:lang="ru">Окно входа</caption>
|
||||||
<caption xml:lang="sa">लॉगिन् स्क्रीन</caption>
|
<caption xml:lang="sa">लॉगिन् स्क्रीन</caption>
|
||||||
<caption xml:lang="sl">Prijavni zaslon</caption>
|
<caption xml:lang="sl">Prijavni zaslon</caption>
|
||||||
@@ -476,6 +488,8 @@
|
|||||||
<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.08.3" date="2025-11-06"/>
|
||||||
|
<release version="25.08.2" date="2025-10-09"/>
|
||||||
<release version="25.08.1" date="2025-09-11"/>
|
<release version="25.08.1" date="2025-09-11"/>
|
||||||
<release version="25.08.0" date="2025-08-14"/>
|
<release version="25.08.0" date="2025-08-14"/>
|
||||||
<release version="25.04.3" date="2025-07-03"/>
|
<release version="25.04.3" date="2025-07-03"/>
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ Comment[lv]=Tērzējiet „Matrix“ tīklā
|
|||||||
Comment[nl]=Chat op Matrix
|
Comment[nl]=Chat op Matrix
|
||||||
Comment[pl]=Rozmawiaj na Matriksie
|
Comment[pl]=Rozmawiaj na Matriksie
|
||||||
Comment[pt_BR]=Bate papo na Matrix
|
Comment[pt_BR]=Bate papo na Matrix
|
||||||
|
Comment[ro]=Discutați pe Matrix
|
||||||
Comment[ru]=Общение в Matrix
|
Comment[ru]=Общение в Matrix
|
||||||
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
|
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
|
||||||
Comment[sl]=Klepet na Matrixu
|
Comment[sl]=Klepet na Matrixu
|
||||||
|
|||||||
2239
po/ar/neochat.po
2239
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
2071
po/ast/neochat.po
2071
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
2284
po/az/neochat.po
2284
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
2200
po/ca/neochat.po
2200
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2375
po/cs/neochat.po
2375
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
2395
po/da/neochat.po
2395
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
2447
po/de/neochat.po
2447
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
2343
po/el/neochat.po
2343
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
2441
po/en_GB/neochat.po
2441
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
2453
po/eo/neochat.po
2453
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
2125
po/es/neochat.po
2125
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
2249
po/eu/neochat.po
2249
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
2559
po/fi/neochat.po
2559
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
2263
po/fr/neochat.po
2263
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
2607
po/gl/neochat.po
2607
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
2203
po/he/neochat.po
2203
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
2445
po/hi/neochat.po
2445
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
2601
po/hu/neochat.po
2601
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
2287
po/ia/neochat.po
2287
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
2356
po/id/neochat.po
2356
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
2228
po/ie/neochat.po
2228
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
2208
po/it/neochat.po
2208
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
2071
po/ja/neochat.po
2071
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
2221
po/ka/neochat.po
2221
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
2577
po/ko/neochat.po
2577
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
2818
po/lt/neochat.po
2818
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
2495
po/lv/neochat.po
2495
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
2205
po/nl/neochat.po
2205
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
2321
po/nn/neochat.po
2321
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
2272
po/pa/neochat.po
2272
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
2997
po/pl/neochat.po
2997
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
2354
po/pt/neochat.po
2354
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
2225
po/pt_BR/neochat.po
2225
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
6548
po/ro/neochat.po
Normal file
6548
po/ro/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
2569
po/ru/neochat.po
2569
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
2445
po/sa/neochat.po
2445
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
2288
po/sk/neochat.po
2288
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
2273
po/sl/neochat.po
2273
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
2220
po/sv/neochat.po
2220
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
2463
po/ta/neochat.po
2463
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
2191
po/tok/neochat.po
2191
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
2271
po/tr/neochat.po
2271
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
2207
po/uk/neochat.po
2207
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
2200
po/zh_CN/neochat.po
2200
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
2554
po/zh_TW/neochat.po
2554
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -48,8 +48,6 @@ if(ANDROID OR WIN32)
|
|||||||
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
|
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
|
||||||
QT_QML_SOURCE_TYPENAME GlobalMenu
|
QT_QML_SOURCE_TYPENAME GlobalMenu
|
||||||
)
|
)
|
||||||
else()
|
|
||||||
set(EXTRA_IMPORTS org.kde.purpose)
|
|
||||||
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
|
||||||
@@ -106,7 +104,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
|||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
QtCore
|
QtCore
|
||||||
QtQuick
|
QtQuick
|
||||||
io.github.quotient_im.libquotient
|
|
||||||
IMPORTS
|
IMPORTS
|
||||||
org.kde.neochat.libneochat
|
org.kde.neochat.libneochat
|
||||||
org.kde.neochat.rooms
|
org.kde.neochat.rooms
|
||||||
@@ -118,15 +115,13 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
|||||||
org.kde.neochat.devtools
|
org.kde.neochat.devtools
|
||||||
org.kde.neochat.login
|
org.kde.neochat.login
|
||||||
org.kde.neochat.chatbar
|
org.kde.neochat.chatbar
|
||||||
org.kde.config
|
|
||||||
org.kde.syntaxhighlighting
|
|
||||||
${EXTRA_IMPORTS}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if(NOT ANDROID AND NOT WIN32)
|
if(NOT ANDROID AND NOT WIN32)
|
||||||
qt_target_qml_sources(neochat QML_FILES
|
qt_target_qml_sources(neochat QML_FILES
|
||||||
qml/ShareAction.qml
|
qml/ShareAction.qml
|
||||||
qml/GlobalMenu.qml
|
qml/GlobalMenu.qml
|
||||||
|
qml/EditMenu.qml
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
qt_target_qml_sources(neochat QML_FILES
|
qt_target_qml_sources(neochat QML_FILES
|
||||||
@@ -343,7 +338,14 @@ if(TARGET KF6::DBusAddons AND NOT WIN32)
|
|||||||
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
|
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (TARGET KF6::KIOWidgets)
|
||||||
|
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
|
||||||
|
endif()
|
||||||
|
|
||||||
if (TARGET KUnifiedPush)
|
if (TARGET KUnifiedPush)
|
||||||
|
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
|
||||||
|
target_link_libraries(neochat PUBLIC KUnifiedPush)
|
||||||
|
|
||||||
if (NOT ANDROID)
|
if (NOT ANDROID)
|
||||||
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
|
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
|
||||||
#include <Quotient/connection.h>
|
#include <Quotient/connection.h>
|
||||||
|
#include <qt6keychain/keychain.h>
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
@@ -13,16 +14,17 @@
|
|||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include <Quotient/csapi/notifications.h>
|
||||||
#include <Quotient/events/roommemberevent.h>
|
#include <Quotient/events/roommemberevent.h>
|
||||||
#include <Quotient/qt_connection_util.h>
|
#include <Quotient/qt_connection_util.h>
|
||||||
#include <Quotient/settings.h>
|
#include <Quotient/settings.h>
|
||||||
|
|
||||||
#include "accountmanager.h"
|
#include "accountmanager.h"
|
||||||
#include "enums/roomsortparameter.h"
|
#include "enums/roomsortparameter.h"
|
||||||
#include "general_logging.h"
|
|
||||||
#include "mediasizehelper.h"
|
#include "mediasizehelper.h"
|
||||||
#include "models/actionsmodel.h"
|
#include "models/actionsmodel.h"
|
||||||
#include "models/messagemodel.h"
|
#include "models/messagemodel.h"
|
||||||
|
#include "models/pushrulemodel.h"
|
||||||
#include "models/roomlistmodel.h"
|
#include "models/roomlistmodel.h"
|
||||||
#include "models/roomtreemodel.h"
|
#include "models/roomtreemodel.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
@@ -38,6 +40,14 @@
|
|||||||
#include "trayicon_sni.h"
|
#include "trayicon_sni.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusMessage>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_KUNIFIEDPUSH
|
#ifdef HAVE_KUNIFIEDPUSH
|
||||||
#include <kunifiedpush/connector.h>
|
#include <kunifiedpush/connector.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -126,10 +136,8 @@ Controller::Controller(QObject *parent)
|
|||||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
delete m_trayIcon;
|
delete m_trayIcon;
|
||||||
#endif
|
|
||||||
NeoChatConfig::self()->save();
|
NeoChatConfig::self()->save();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -195,15 +203,13 @@ void Controller::setAccountManager(AccountManager *manager)
|
|||||||
|
|
||||||
m_accountManager = manager;
|
m_accountManager = manager;
|
||||||
|
|
||||||
if (!m_accountManager) {
|
if (m_accountManager) {
|
||||||
return;
|
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
||||||
|
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
||||||
|
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
||||||
|
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
||||||
|
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
|
||||||
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
|
||||||
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
|
||||||
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
|
||||||
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::initConnection(NeoChatConnection *connection)
|
void Controller::initConnection(NeoChatConnection *connection)
|
||||||
@@ -259,8 +265,8 @@ bool Controller::supportSystemTray() const
|
|||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
return false;
|
return false;
|
||||||
#else
|
#else
|
||||||
QStringList unsupportedPlatforms{u"GNOME"_s, u"Pantheon"_s};
|
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
|
||||||
return !unsupportedPlatforms.contains(QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP")));
|
return de != u"GNOME"_s && de != u"Pantheon"_s;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,8 +276,11 @@ void Controller::setQuitOnLastWindowClosed()
|
|||||||
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
|
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
|
||||||
m_trayIcon = new TrayIcon(this);
|
m_trayIcon = new TrayIcon(this);
|
||||||
m_trayIcon->show();
|
m_trayIcon->show();
|
||||||
} else if (m_trayIcon) {
|
} else {
|
||||||
delete m_trayIcon;
|
if (m_trayIcon) {
|
||||||
|
delete m_trayIcon;
|
||||||
|
m_trayIcon = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -309,8 +318,7 @@ void Controller::listenForNotifications()
|
|||||||
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
|
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
|
||||||
|
|
||||||
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
|
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
|
||||||
instance().m_notificationsManager.postPushNotification(data);
|
NotificationsManager::postPushNotification(data);
|
||||||
timer->stop();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
|
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
|
||||||
@@ -328,7 +336,30 @@ void Controller::clearInvitationNotification(const QString &roomId)
|
|||||||
|
|
||||||
void Controller::updateBadgeNotificationCount(int count)
|
void Controller::updateBadgeNotificationCount(int count)
|
||||||
{
|
{
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
|
// copied from Telegram desktop
|
||||||
|
const auto launcherUrl = "application://org.kde.neochat.desktop"_L1;
|
||||||
|
// Gnome requires that count is a 64bit integer
|
||||||
|
const qint64 counterSlice = std::min(count, 9999);
|
||||||
|
QVariantMap dbusUnityProperties;
|
||||||
|
|
||||||
|
if (counterSlice > 0) {
|
||||||
|
dbusUnityProperties["count"_L1] = counterSlice;
|
||||||
|
dbusUnityProperties["count-visible"_L1] = true;
|
||||||
|
} else {
|
||||||
|
dbusUnityProperties["count-visible"_L1] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_L1, "com.canonical.Unity.LauncherEntry"_L1, "Update"_L1);
|
||||||
|
|
||||||
|
signal.setArguments({launcherUrl, dbusUnityProperties});
|
||||||
|
|
||||||
|
QDBusConnection::sessionBus().send(signal);
|
||||||
|
#endif // Q_OS_ANDROID
|
||||||
|
#else
|
||||||
qGuiApp->setBadgeNumber(count);
|
qGuiApp->setBadgeNumber(count);
|
||||||
|
#endif // QT_VERSION_CHECK(6, 6, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Controller::isFlatpak() const
|
bool Controller::isFlatpak() const
|
||||||
@@ -349,10 +380,7 @@ QString Controller::loadFileContent(const QString &path) const
|
|||||||
{
|
{
|
||||||
QUrl url(path);
|
QUrl url(path);
|
||||||
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||||
if (!file.open(QFile::ReadOnly)) {
|
file.open(QFile::ReadOnly);
|
||||||
qCWarning(GENERAL) << "Failed to open file" << path;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return QString::fromLatin1(file.readAll());
|
return QString::fromLatin1(file.readAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,9 +110,8 @@ private:
|
|||||||
void initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection);
|
void initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection);
|
||||||
|
|
||||||
QPointer<NeoChatConnection> m_connection;
|
QPointer<NeoChatConnection> m_connection;
|
||||||
#ifndef Q_OS_ANDROID
|
TrayIcon *m_trayIcon = nullptr;
|
||||||
QPointer<TrayIcon> m_trayIcon;
|
|
||||||
#endif
|
|
||||||
QString m_endpoint;
|
QString m_endpoint;
|
||||||
QStringList m_shownImages;
|
QStringList m_shownImages;
|
||||||
|
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ int main(int argc, char *argv[])
|
|||||||
parser.addOption(testOption);
|
parser.addOption(testOption);
|
||||||
|
|
||||||
#ifdef HAVE_KUNIFIEDPUSH
|
#ifdef HAVE_KUNIFIEDPUSH
|
||||||
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s, i18n("Internal usage only."));
|
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s);
|
||||||
dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
|
dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
|
||||||
parser.addOption(dbusActivatedOption);
|
parser.addOption(dbusActivatedOption);
|
||||||
#endif
|
#endif
|
||||||
@@ -219,8 +219,14 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
#ifdef HAVE_KUNIFIEDPUSH
|
#ifdef HAVE_KUNIFIEDPUSH
|
||||||
if (parser.isSet(dbusActivatedOption)) {
|
if (parser.isSet(dbusActivatedOption)) {
|
||||||
// We want to be replaceable by the main client
|
#ifdef HAVE_KDBUSADDONS
|
||||||
KDBusService service(KDBusService::Replace);
|
// We *don't* want to use KDBusService here. I don't know why, but it makes activation super unreliable. We don't really need it anyway.
|
||||||
|
if (!QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.neochat"))) {
|
||||||
|
// Gracefully fail if NeoChat is already running
|
||||||
|
qWarning() << "NeoChat already running, not sending push notifications.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_RUNNER
|
#ifdef HAVE_RUNNER
|
||||||
// If we are built with KRunner and KUnifiedPush support, we need to do something special.
|
// If we are built with KRunner and KUnifiedPush support, we need to do something special.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
#include <Quotient/events/roommessageevent.h>
|
#include <Quotient/events/roommessageevent.h>
|
||||||
#include <Quotient/roommember.h>
|
#include <Quotient/roommember.h>
|
||||||
@@ -24,7 +25,11 @@ class CommonRoomsModel : public QAbstractListModel
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
enum Roles {
|
enum Roles {
|
||||||
RoomIdRole = Qt::DisplayRole,
|
TextRole = Qt::DisplayRole,
|
||||||
|
LongitudeRole,
|
||||||
|
LatitudeRole,
|
||||||
|
AssetRole,
|
||||||
|
AuthorRole,
|
||||||
};
|
};
|
||||||
Q_ENUM(Roles)
|
Q_ENUM(Roles)
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ QVariant NotificationsModel::data(const QModelIndex &index, int role) const
|
|||||||
QHash<int, QByteArray> NotificationsModel::roleNames() const
|
QHash<int, QByteArray> NotificationsModel::roleNames() const
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
{TextRole, "notificationText"},
|
{TextRole, "text"},
|
||||||
{RoomIdRole, "roomId"},
|
{RoomIdRole, "roomId"},
|
||||||
{AuthorName, "authorName"},
|
{AuthorName, "authorName"},
|
||||||
{AuthorAvatar, "authorAvatar"},
|
{AuthorAvatar, "authorAvatar"},
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ Name[pa]=ਨਵਾਂ ਸੱਦਾ
|
|||||||
Name[pl]=Nowe zaproszenie
|
Name[pl]=Nowe zaproszenie
|
||||||
Name[pt]=Novo Convite
|
Name[pt]=Novo Convite
|
||||||
Name[pt_BR]=Novo convite
|
Name[pt_BR]=Novo convite
|
||||||
|
Name[ro]=Invitație nouă
|
||||||
Name[ru]=Новое приглашение
|
Name[ru]=Новое приглашение
|
||||||
Name[sa]=नवीन आमन्त्रणम्
|
Name[sa]=नवीन आमन्त्रणम्
|
||||||
Name[sl]=Novo povabilo
|
Name[sl]=Novo povabilo
|
||||||
@@ -252,6 +253,7 @@ Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
|
|||||||
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
|
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
|
||||||
Comment[pt]=Existe um novo convite para uma sala
|
Comment[pt]=Existe um novo convite para uma sala
|
||||||
Comment[pt_BR]=Existe um novo convite para uma sala
|
Comment[pt_BR]=Existe um novo convite para uma sala
|
||||||
|
Comment[ro]=E o nouă invitație la o cameră
|
||||||
Comment[ru]=Доступно новое приглашение в комнату
|
Comment[ru]=Доступно новое приглашение в комнату
|
||||||
Comment[sa]=कक्षस्य नूतनं निमन्त्रणम् अस्ति
|
Comment[sa]=कक्षस्य नूतनं निमन्त्रणम् अस्ति
|
||||||
Comment[sl]=Tam je novo povabilo v sobo
|
Comment[sl]=Tam je novo povabilo v sobo
|
||||||
@@ -290,6 +292,7 @@ Name[nl]=Gedeelde
|
|||||||
Name[nn]=Del
|
Name[nn]=Del
|
||||||
Name[pl]=Udostępnij
|
Name[pl]=Udostępnij
|
||||||
Name[pt_BR]=Compartilhar
|
Name[pt_BR]=Compartilhar
|
||||||
|
Name[ro]=Partajare
|
||||||
Name[ru]=Публикация
|
Name[ru]=Публикация
|
||||||
Name[sa]=संविभागः
|
Name[sa]=संविभागः
|
||||||
Name[sl]=Deli
|
Name[sl]=Deli
|
||||||
@@ -324,6 +327,7 @@ Comment[nl]=Het resultaat van het delen van een stukje inhoud
|
|||||||
Comment[nn]=Resultatet av deling av innhald
|
Comment[nn]=Resultatet av deling av innhald
|
||||||
Comment[pl]=Wynik udostępniania kawałka treści
|
Comment[pl]=Wynik udostępniania kawałka treści
|
||||||
Comment[pt_BR]=O resultado de compartilhar um conteúdo
|
Comment[pt_BR]=O resultado de compartilhar um conteúdo
|
||||||
|
Comment[ro]=Rezultatul partajării unei bucăți de conținut
|
||||||
Comment[ru]=Результат публикации данных
|
Comment[ru]=Результат публикации данных
|
||||||
Comment[sa]=सामग्रीखण्डस्य साझाकरणस्य परिणामः
|
Comment[sa]=सामग्रीखण्डस्य साझाकरणस्य परिणामः
|
||||||
Comment[sl]=Rezultat deljenega kosa vsebine
|
Comment[sl]=Rezultat deljenega kosa vsebine
|
||||||
|
|||||||
@@ -189,10 +189,6 @@
|
|||||||
<label>Don't hide any events in the timeline</label>
|
<label>Don't hide any events in the timeline</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="RelateAnyEvent" type="bool">
|
|
||||||
<label>Send relations to any event, including state events and events normally hidden.</label>
|
|
||||||
<default>false</default>
|
|
||||||
</entry>
|
|
||||||
<entry name="AlwaysVerifyDevice" type="bool">
|
<entry name="AlwaysVerifyDevice" type="bool">
|
||||||
<label>Always allow device verification</label>
|
<label>Always allow device verification</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
|
|||||||
@@ -60,8 +60,9 @@ void NotificationsManager::startNotificationJob(QPointer<NeoChatConnection> conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!m_connActiveJob.contains(connection->user()->id())) {
|
if (!m_connActiveJob.contains(connection->user()->id())) {
|
||||||
|
auto job = connection->callApi<GetNotificationsJob>();
|
||||||
m_connActiveJob.append(connection->user()->id());
|
m_connActiveJob.append(connection->user()->id());
|
||||||
connection->callApi<GetNotificationsJob>().onResult([this, connection](const auto &job) {
|
connect(job, &BaseJob::success, this, [this, job, connection]() {
|
||||||
m_connActiveJob.removeAll(connection->user()->id());
|
m_connActiveJob.removeAll(connection->user()->id());
|
||||||
processNotificationJob(connection, job, !m_oldNotifications.contains(connection->user()->id()));
|
processNotificationJob(connection, job, !m_oldNotifications.contains(connection->user()->id()));
|
||||||
});
|
});
|
||||||
@@ -388,7 +389,7 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
|
|||||||
|
|
||||||
#ifdef HAVE_KIO
|
#ifdef HAVE_KIO
|
||||||
auto openAction = notification->addAction(i18n("Open NeoChat"));
|
auto openAction = notification->addAction(i18n("Open NeoChat"));
|
||||||
connect(openAction, &KNotificationAction::activated, this, [=]() {
|
connect(openAction, &KNotificationAction::activated, notification, [=]() {
|
||||||
QString properId = roomId;
|
QString properId = roomId;
|
||||||
properId = properId.replace(u"#"_s, QString());
|
properId = properId.replace(u"#"_s, QString());
|
||||||
properId = properId.replace(u"!"_s, QString());
|
properId = properId.replace(u"!"_s, QString());
|
||||||
@@ -402,8 +403,6 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
|
|||||||
connect(notification, &KNotification::closed, qGuiApp, &QGuiApplication::quit);
|
connect(notification, &KNotification::closed, qGuiApp, &QGuiApplication::quit);
|
||||||
|
|
||||||
notification->sendEvent();
|
notification->sendEvent();
|
||||||
|
|
||||||
m_notifications.insert(roomId, {json["ts"_L1].toVariant().toLongLong(), notification});
|
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Skipping unsupported push notification" << type;
|
qWarning() << "Skipping unsupported push notification" << type;
|
||||||
}
|
}
|
||||||
@@ -432,7 +431,7 @@ QPixmap NotificationsManager::createNotificationImage(const QImage &icon, NeoCha
|
|||||||
|
|
||||||
if (room != nullptr) {
|
if (room != nullptr) {
|
||||||
const QImage roomAvatar = room->avatar(imageRect.width(), imageRect.height());
|
const QImage roomAvatar = room->avatar(imageRect.width(), imageRect.height());
|
||||||
if (!roomAvatar.isNull() && icon != roomAvatar) {
|
if (icon != roomAvatar) {
|
||||||
const QRect lowerQuarter{imageRect.center(), imageRect.size() / 2};
|
const QRect lowerQuarter{imageRect.center(), imageRect.size() / 2};
|
||||||
|
|
||||||
painter.setBrush(Qt::white);
|
painter.setBrush(Qt::white);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Display a native notification for the given push notification.
|
* @brief Display a native notification for the given push notification.
|
||||||
*/
|
*/
|
||||||
void postPushNotification(const QByteArray &message);
|
static void postPushNotification(const QByteArray &message);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Handle the notifications for the given connection.
|
* @brief Handle the notifications for the given connection.
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ Comment[nn]=Finn rom i NeoChat
|
|||||||
Comment[pl]=Znajdź pokoje w NeoChat
|
Comment[pl]=Znajdź pokoje w NeoChat
|
||||||
Comment[pt]=Procurar salas no NeoChat
|
Comment[pt]=Procurar salas no NeoChat
|
||||||
Comment[pt_BR]=Encontrar salas no NeoChat
|
Comment[pt_BR]=Encontrar salas no NeoChat
|
||||||
|
Comment[ro]=Găsește camere în NeoChat
|
||||||
Comment[ru]=Поиск комнат NeoChat
|
Comment[ru]=Поиск комнат NeoChat
|
||||||
Comment[sa]=NeoChat इत्यत्र कक्ष्याः अन्वेषणं कुर्वन्तु
|
Comment[sa]=NeoChat इत्यत्र कक्ष्याः अन्वेषणं कुर्वन्तु
|
||||||
Comment[sl]=Najdi sobe v NeoChatu
|
Comment[sl]=Najdi sobe v NeoChatu
|
||||||
@@ -91,3 +92,4 @@ X-Plasma-API=DBus
|
|||||||
X-Plasma-DBusRunner-Service=org.kde.neochat
|
X-Plasma-DBusRunner-Service=org.kde.neochat
|
||||||
X-Plasma-DBusRunner-Path=/RoomRunner
|
X-Plasma-DBusRunner-Path=/RoomRunner
|
||||||
X-Plasma-Request-Actions-Once=true
|
X-Plasma-Request-Actions-Once=true
|
||||||
|
X-Plasma-Runner-Min-Letter-Count=3
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.components as KirigamiComponents
|
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
import org.kde.neochat.settings
|
import org.kde.neochat.settings
|
||||||
|
import org.kde.neochat.devtools
|
||||||
|
|
||||||
KirigamiComponents.ConvergentContextMenu {
|
KirigamiComponents.ConvergentContextMenu {
|
||||||
id: root
|
id: root
|
||||||
@@ -18,17 +18,21 @@ KirigamiComponents.ConvergentContextMenu {
|
|||||||
required property NeoChatConnection connection
|
required property NeoChatConnection connection
|
||||||
required property Kirigami.ApplicationWindow window
|
required property Kirigami.ApplicationWindow window
|
||||||
|
|
||||||
Kirigami.Action {
|
QQC2.Action {
|
||||||
text: i18nc("@action:button", "Show QR Code")
|
text: i18nc("@action:button", "Show QR Code")
|
||||||
icon.name: "view-barcode-qr-symbolic"
|
icon.name: "view-barcode-qr-symbolic"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
(Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
let qrMax = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||||
text: "https://matrix.to/#/" + root.connection.localUser.id,
|
text: "https://matrix.to/#/" + root.connection.localUser.id,
|
||||||
title: root.connection.localUser.displayName,
|
title: root.connection.localUser.displayName,
|
||||||
subtitle: root.connection.localUser.id,
|
subtitle: root.connection.localUser.id,
|
||||||
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
|
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
|
||||||
avatarSource: root.connection.localUser.avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(root.connection.localUser.avatarUrl) : ""
|
avatarSource: root.connection.localUser.avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(root.connection.localUser.avatarUrl) : ""
|
||||||
}) as QrCodeMaximizeComponent).open();
|
});
|
||||||
|
if (typeof root.closeDialog === "function") {
|
||||||
|
root.closeDialog();
|
||||||
|
}
|
||||||
|
qrMax.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,27 +40,26 @@ KirigamiComponents.ConvergentContextMenu {
|
|||||||
text: i18nc("@action:inmenu", "Switch Account")
|
text: i18nc("@action:inmenu", "Switch Account")
|
||||||
icon.name: "system-switch-user"
|
icon.name: "system-switch-user"
|
||||||
shortcut: "Ctrl+U"
|
shortcut: "Ctrl+U"
|
||||||
onTriggered: (Qt.createComponent("org.kde.neochat", "AccountSwitchDialog").createObject(QQC2.Overlay.overlay, {
|
onTriggered: accountSwitchDialog.createObject(QQC2.Overlay.overlay, {
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
}) as Kirigami.Dialog).open();
|
}).open();
|
||||||
}
|
}
|
||||||
|
QQC2.Action {
|
||||||
Kirigami.Action {
|
text: i18n("Edit This Account")
|
||||||
text: i18nc("@action:inmenu", "Edit This Account")
|
|
||||||
icon.name: "document-edit"
|
icon.name: "document-edit"
|
||||||
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection});
|
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection});
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Action {
|
QQC2.Action {
|
||||||
text: i18nc("@action:inmenu", "Notification Settings")
|
text: i18n("Notification Settings")
|
||||||
icon.name: "notifications"
|
icon.name: "notifications"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
NeoChatSettingsView.open('notifications');
|
NeoChatSettingsView.open('notifications');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Action {
|
QQC2.Action {
|
||||||
text: i18nc("@action:inmenu", "Devices")
|
text: i18n("Devices")
|
||||||
icon.name: "computer-symbolic"
|
icon.name: "computer-symbolic"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
NeoChatSettingsView.open('devices');
|
NeoChatSettingsView.open('devices');
|
||||||
@@ -64,10 +67,10 @@ KirigamiComponents.ConvergentContextMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
text: i18nc("@action:inmenu", "Open Developer Tools")
|
text: i18n("Open Developer Tools")
|
||||||
icon.name: "tools"
|
icon.name: "tools"
|
||||||
visible: NeoChatConfig.developerTools
|
visible: NeoChatConfig.developerTools
|
||||||
onTriggered: root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
|
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
}, {
|
}, {
|
||||||
title: i18nc("@title:window", "Developer Tools"),
|
title: i18nc("@title:window", "Developer Tools"),
|
||||||
@@ -85,10 +88,9 @@ KirigamiComponents.ConvergentContextMenu {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Action {
|
QQC2.Action {
|
||||||
text: i18nc("@action:inmenu", "Verify This Device")
|
text: i18nc("@action:inmenu", "Verify This Device")
|
||||||
icon.name: "security-low"
|
icon.name: "security-low"
|
||||||
visible: !root.connection.isVerifiedSession
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
root.connection.startSelfVerification();
|
root.connection.startSelfVerification();
|
||||||
const dialog = Qt.createComponent("org.kde.kirigami", "PromptDialog").createObject(QQC2.Overlay.overlay, {
|
const dialog = Qt.createComponent("org.kde.kirigami", "PromptDialog").createObject(QQC2.Overlay.overlay, {
|
||||||
@@ -97,17 +99,19 @@ KirigamiComponents.ConvergentContextMenu {
|
|||||||
standardButtons: Kirigami.Dialog.Ok
|
standardButtons: Kirigami.Dialog.Ok
|
||||||
})
|
})
|
||||||
dialog.open();
|
dialog.open();
|
||||||
root.connection.newKeyVerificationSession.connect(() => {
|
root.connection.onNewKeyVerificationSession.connect(() => {
|
||||||
dialog.close();
|
dialog.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Action {
|
QQC2.Action {
|
||||||
text: i18nc("@action:inmenu", "Logout…")
|
text: i18n("Logout")
|
||||||
icon.name: "im-kick-user"
|
icon.name: "im-kick-user"
|
||||||
onTriggered: (Qt.createComponent("org.kde.neochat", "ConfirmLogoutDialog").createObject(QQC2.Overlay.overlay, {
|
onTriggered: confirmLogoutDialogComponent.createObject(root).open()
|
||||||
connection: root.connection
|
}
|
||||||
}) as Kirigami.Dialog).open()
|
|
||||||
|
readonly property Component confirmLogoutDialogComponent: ConfirmLogoutDialog {
|
||||||
|
connection: root.connection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
@@ -19,6 +16,8 @@ Kirigami.Dialog {
|
|||||||
|
|
||||||
required property NeoChatConnection connection
|
required property NeoChatConnection connection
|
||||||
|
|
||||||
|
parent: applicationWindow().overlay
|
||||||
|
|
||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
topPadding: 0
|
topPadding: 0
|
||||||
@@ -26,7 +25,7 @@ Kirigami.Dialog {
|
|||||||
|
|
||||||
standardButtons: Kirigami.Dialog.NoButton
|
standardButtons: Kirigami.Dialog.NoButton
|
||||||
|
|
||||||
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
|
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||||
title: i18nc("@title: dialog to switch between logged in accounts", "Switch Account")
|
title: i18nc("@title: dialog to switch between logged in accounts", "Switch Account")
|
||||||
|
|
||||||
onVisibleChanged: if (visible) {
|
onVisibleChanged: if (visible) {
|
||||||
@@ -54,8 +53,8 @@ Kirigami.Dialog {
|
|||||||
}
|
}
|
||||||
text: i18nc("@button: login to or register a new account.", "Add Account")
|
text: i18nc("@button: login to or register a new account.", "Add Account")
|
||||||
contentItem: Delegates.SubtitleContentItem {
|
contentItem: Delegates.SubtitleContentItem {
|
||||||
itemDelegate: addDelegate
|
itemDelegate: parent
|
||||||
subtitle: i18nc("@info", "Log in or create a new account")
|
subtitle: i18n("Log in or create a new account")
|
||||||
labelItem.textFormat: Text.PlainText
|
labelItem.textFormat: Text.PlainText
|
||||||
subtitleItem.textFormat: Text.PlainText
|
subtitleItem.textFormat: Text.PlainText
|
||||||
}
|
}
|
||||||
@@ -95,8 +94,8 @@ Kirigami.Dialog {
|
|||||||
accountView.decrementCurrentIndex();
|
accountView.decrementCurrentIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keys.onEnterPressed: (accountView.currentItem as Delegates.RoundedItemDelegate ?? accountView.footerItem).clicked()
|
Keys.onEnterPressed: accountView.currentItem.clicked()
|
||||||
Keys.onReturnPressed: (accountView.currentItem as Delegates.RoundedItemDelegate ?? accountView.footerItem).clicked()
|
Keys.onReturnPressed: accountView.currentItem.clicked()
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
for (let i = 0; i < accountView.count; i++) {
|
for (let i = 0; i < accountView.count; i++) {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import QtQuick.Controls as QQC2
|
|||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat
|
||||||
|
|
||||||
Kirigami.Dialog {
|
Kirigami.Dialog {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
@@ -18,7 +20,7 @@ Kirigami.Dialog {
|
|||||||
title: i18nc("@title:dialog", "Start a chat")
|
title: i18nc("@title:dialog", "Start a chat")
|
||||||
|
|
||||||
contentItem: QQC2.Label {
|
contentItem: QQC2.Label {
|
||||||
text: i18nc("@info", "Do you want to start a chat with %1?", root.user.displayName)
|
text: i18n("Do you want to start a chat with %1?", root.user.displayName)
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
horizontalAlignment: Qt.AlignHCenter
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
id: editImageButton
|
id: editImageButton
|
||||||
visible: root.hasImage
|
visible: hasImage
|
||||||
icon.name: "document-edit"
|
icon.name: "document-edit"
|
||||||
text: i18n("Edit")
|
text: i18n("Edit")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
@@ -46,9 +46,9 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
let imageEditor = (Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(imageEditorPage);
|
let imageEditor = applicationWindow().pageStack.pushDialogLayer(imageEditorPage);
|
||||||
imageEditor.newPathChanged.connect(function (newPath) {
|
imageEditor.newPathChanged.connect(function (newPath) {
|
||||||
imageEditor.closeDialog();
|
applicationWindow().pageStack.layers.pop();
|
||||||
root.attachmentPath = newPath;
|
root.attachmentPath = newPath;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -58,18 +58,14 @@ ColumnLayout {
|
|||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
id: cancelAttachmentButton
|
id: cancelAttachmentButton
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
text: i18nc("@action:button", "Cancel sending attachment")
|
action: Kirigami.Action {
|
||||||
icon.name: "dialog-close"
|
text: i18n("Cancel sending attachment")
|
||||||
onClicked: root.attachmentCancelled()
|
icon.name: "dialog-close"
|
||||||
|
onTriggered: attachmentCancelled()
|
||||||
|
shortcut: "Escape"
|
||||||
|
}
|
||||||
QQC2.ToolTip.text: text
|
QQC2.ToolTip.text: text
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
||||||
|
|
||||||
Kirigami.Action {
|
|
||||||
shortcut: "Escape"
|
|
||||||
onTriggered: cancelAttachmentButton.clicked()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,8 +75,8 @@ ColumnLayout {
|
|||||||
|
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
||||||
source: root.hasImage ? root.attachmentPath : ""
|
source: hasImage ? root.attachmentPath : ""
|
||||||
visible: root.hasImage
|
visible: hasImage
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
onSourceChanged: {
|
onSourceChanged: {
|
||||||
@@ -118,11 +114,11 @@ ColumnLayout {
|
|||||||
id: mimetypeIcon
|
id: mimetypeIcon
|
||||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||||
source: root.attachmentMimetype.iconName
|
source: attachmentMimetype.iconName
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: fileLabel
|
id: fileLabel
|
||||||
text: root.baseFileName
|
text: baseFileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Templates as T
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.delegates as Delegates
|
import org.kde.kirigamiaddons.delegates as Delegates
|
||||||
|
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||||
|
|
||||||
Delegates.RoundedItemDelegate {
|
Delegates.RoundedItemDelegate {
|
||||||
id: root
|
id: root
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Components.AbstractMaximizeComponent {
|
|||||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||||
|
|
||||||
name: root.author.displayName
|
name: root.author.name ?? root.author.displayName
|
||||||
source: root.author.avatarUrl
|
source: root.author.avatarUrl
|
||||||
color: root.author.color
|
color: root.author.color
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ Components.AbstractMaximizeComponent {
|
|||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: userLabel
|
id: userLabel
|
||||||
|
|
||||||
text: root.author.displayName
|
text: root.author.name ?? root.author.displayName
|
||||||
color: root.author.color
|
color: root.author.color
|
||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import QtQml.Models
|
|||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||||
|
import org.kde.kitemmodels
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
|
||||||
|
|||||||
86
src/app/qml/EditMenu.qml
Normal file
86
src/app/qml/EditMenu.qml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
import Qt.labs.platform as Labs
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Labs.Menu {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property Item field
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: root.field !== null && root.field.canUndo
|
||||||
|
text: i18nc("text editing menu action", "Undo")
|
||||||
|
shortcut: StandardKey.Undo
|
||||||
|
onTriggered: {
|
||||||
|
root.field.undo();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: root.field !== null && root.field.canRedo
|
||||||
|
text: i18nc("text editing menu action", "Redo")
|
||||||
|
shortcut: StandardKey.Redo
|
||||||
|
onTriggered: {
|
||||||
|
root.field.undo();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuSeparator {}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: root.field !== null && root.field.selectedText
|
||||||
|
text: i18nc("text editing menu action", "Cut")
|
||||||
|
shortcut: StandardKey.Cut
|
||||||
|
onTriggered: {
|
||||||
|
root.field.cut();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: root.field !== null && root.field.selectedText
|
||||||
|
text: i18nc("text editing menu action", "Copy")
|
||||||
|
shortcut: StandardKey.Copy
|
||||||
|
onTriggered: {
|
||||||
|
root.field.copy();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: root.field !== null && root.field.canPaste
|
||||||
|
text: i18nc("text editing menu action", "Paste")
|
||||||
|
shortcut: StandardKey.Paste
|
||||||
|
onTriggered: {
|
||||||
|
root.field.paste();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: root.field !== null && root.field.selectedText !== ""
|
||||||
|
text: i18nc("text editing menu action", "Delete")
|
||||||
|
shortcut: ""
|
||||||
|
onTriggered: {
|
||||||
|
root.field.remove(root.field.selectionStart, root.field.selectionEnd);
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuSeparator {}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: root.field !== null
|
||||||
|
text: i18nc("text editing menu action", "Select All")
|
||||||
|
shortcut: StandardKey.SelectAll
|
||||||
|
onTriggered: {
|
||||||
|
root.field.selectAll();
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,15 +6,16 @@ import QtQuick.Controls as QQC2
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.neochat
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property string emoji
|
property alias emoji: emojiLabel.text
|
||||||
required property string description
|
property alias description: descriptionLabel.text
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
text: root.emoji
|
id: emojiLabel
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||||
@@ -24,7 +25,7 @@ ColumnLayout {
|
|||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
text: root.description
|
id: descriptionLabel
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ RowLayout {
|
|||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
id: repeater
|
id: repeater
|
||||||
delegate: EmojiItem {}
|
delegate: EmojiItem {
|
||||||
|
emoji: modelData.emoji
|
||||||
|
description: modelData.description
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Qt.labs.platform as Labs
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Window
|
import QtQuick.Window
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
@@ -15,50 +16,12 @@ Labs.MenuBar {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property NeoChatConnection connection
|
required property NeoChatConnection connection
|
||||||
required property Kirigami.ApplicationWindow appWindow
|
|
||||||
|
|
||||||
Labs.Menu {
|
Labs.Menu {
|
||||||
title: i18nc("menu", "File")
|
title: i18nc("menu", "NeoChat")
|
||||||
|
|
||||||
Labs.MenuItem {
|
Labs.MenuItem {
|
||||||
icon.name: "list-add-user"
|
enabled: pageStack.layers.currentItem.title !== i18n("Configure NeoChat…")
|
||||||
text: i18nc("@action:inmenu", "Find your Friends")
|
|
||||||
enabled: root.connection
|
|
||||||
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
|
|
||||||
connection: root.connection
|
|
||||||
}, {
|
|
||||||
title: i18nc("@title", "Find your friends")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Labs.MenuItem {
|
|
||||||
icon.name: "system-users-symbolic"
|
|
||||||
text: i18nc("@action:inmenu", "Create a Room…")
|
|
||||||
enabled: root.connection
|
|
||||||
shortcut: StandardKey.New
|
|
||||||
onTriggered: {
|
|
||||||
Qt.createComponent('org.kde.neochat', 'CreateRoomDialog').createObject(root.appWindow, {
|
|
||||||
connection: root.connection
|
|
||||||
}, {
|
|
||||||
title: i18nc("@title", "Create a Room")
|
|
||||||
}).open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Labs.MenuItem {
|
|
||||||
icon.name: "compass-symbolic"
|
|
||||||
text: i18nc("@action:inmenu", "Explore Rooms")
|
|
||||||
enabled: root.connection
|
|
||||||
onTriggered: {
|
|
||||||
let dialog = root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
|
|
||||||
connection: root.connection
|
|
||||||
}, {
|
|
||||||
title: i18nc("@title", "Explore Rooms")
|
|
||||||
});
|
|
||||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
|
||||||
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Labs.MenuItem {
|
|
||||||
text: i18nc("menu", "Configure NeoChat…")
|
text: i18nc("menu", "Configure NeoChat…")
|
||||||
|
|
||||||
shortcut: StandardKey.Preferences
|
shortcut: StandardKey.Preferences
|
||||||
@@ -71,15 +34,58 @@ Labs.MenuBar {
|
|||||||
onTriggered: Qt.quit()
|
onTriggered: Qt.quit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Labs.Menu {
|
||||||
|
title: i18nc("menu", "File")
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
icon.name: "list-add-user"
|
||||||
|
text: i18nc("@action:inmenu", "Find your Friends")
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
|
||||||
|
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
|
||||||
|
connection: root.connection
|
||||||
|
}, {
|
||||||
|
title: i18nc("@title", "Find your friends")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Labs.MenuItem {
|
||||||
|
icon.name: "system-users-symbolic"
|
||||||
|
text: i18nc("@action:inmenu", "Create a Room…")
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
|
||||||
|
shortcut: StandardKey.New
|
||||||
|
onTriggered: {
|
||||||
|
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
|
||||||
|
connection: root.connection
|
||||||
|
}, {
|
||||||
|
title: i18nc("@title", "Create a Room")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Labs.MenuItem {
|
||||||
|
icon.name: "compass-symbolic"
|
||||||
|
text: i18nc("@action:inmenu", "Explore Rooms")
|
||||||
|
onTriggered: {
|
||||||
|
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
|
||||||
|
connection: root.connection
|
||||||
|
}, {
|
||||||
|
title: i18nc("@title", "Explore Rooms")
|
||||||
|
});
|
||||||
|
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||||
|
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EditMenu {
|
||||||
|
title: i18nc("menu", "Edit")
|
||||||
|
field: (root.activeFocusItem instanceof TextEdit || root.activeFocusItem instanceof TextInput) ? root.activeFocusItem : null
|
||||||
|
}
|
||||||
Labs.Menu {
|
Labs.Menu {
|
||||||
title: i18nc("menu", "View")
|
title: i18nc("menu", "View")
|
||||||
|
|
||||||
Labs.MenuItem {
|
Labs.MenuItem {
|
||||||
icon.name: "search-symbolic"
|
icon.name: "search-symbolic"
|
||||||
enabled: root.connection
|
|
||||||
text: i18nc("@action:inmenu opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Search Rooms")
|
text: i18nc("@action:inmenu opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Search Rooms")
|
||||||
onTriggered: (root.appWindow as Main).quickSwitcher.open()
|
onTriggered: quickSwitcher.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Labs.Menu {
|
Labs.Menu {
|
||||||
@@ -87,8 +93,8 @@ Labs.MenuBar {
|
|||||||
|
|
||||||
Labs.MenuItem {
|
Labs.MenuItem {
|
||||||
icon.name: "view-fullscreen-symbolic"
|
icon.name: "view-fullscreen-symbolic"
|
||||||
text: root.appWindow.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
|
text: root.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
|
||||||
onTriggered: root.appWindow.visibility === Window.FullScreen ? root.appWindow.showNormal() : root.appWindow.showFullScreen()
|
onTriggered: root.visibility === Window.FullScreen ? root.showNormal() : root.showFullScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Labs.Menu {
|
Labs.Menu {
|
||||||
@@ -97,12 +103,12 @@ Labs.MenuBar {
|
|||||||
Labs.MenuItem {
|
Labs.MenuItem {
|
||||||
icon.name: "help-about-symbolic"
|
icon.name: "help-about-symbolic"
|
||||||
text: i18nc("menu", "About NeoChat")
|
text: i18nc("menu", "About NeoChat")
|
||||||
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
|
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
|
||||||
}
|
}
|
||||||
Labs.MenuItem {
|
Labs.MenuItem {
|
||||||
icon.name: "kde-symbolic"
|
icon.name: "kde-symbolic"
|
||||||
text: i18nc("menu", "About KDE")
|
text: i18nc("menu", "About KDE")
|
||||||
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage"))
|
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
required property NeoChatConnection connection
|
required property NeoChatConnection connection
|
||||||
required property Kirigami.ApplicationWindow appWindow
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,54 +3,29 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
RowLayout {
|
QQC2.Control {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string text
|
property string text
|
||||||
|
|
||||||
onTextChanged: {
|
visible: !root.text.startsWith("https://matrix.to/") && root.text.length > 0
|
||||||
// This is done so the text doesn't disappear for a split second while in the opacity transition
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
if (root.text.length > 0) {
|
|
||||||
urlLabel.text = root.text
|
z: 20
|
||||||
}
|
|
||||||
|
Accessible.ignored: true
|
||||||
|
|
||||||
|
contentItem: QQC2.Label {
|
||||||
|
text: root.text.startsWith("https://matrix.to/") ? "" : root.text
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Accessible.description: i18nc("@info screenreader", "The currently selected link")
|
||||||
}
|
}
|
||||||
|
|
||||||
z: 99
|
background: Rectangle {
|
||||||
spacing: 0
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
opacity: (!root.text.startsWith("https://matrix.to/") && root.text.length > 0) ? 1 : 0
|
|
||||||
visible: opacity > 0
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
OpacityAnimator {
|
|
||||||
duration: Kirigami.Units.shortDuration
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.Control {
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
|
|
||||||
Accessible.ignored: true
|
|
||||||
|
|
||||||
contentItem: QQC2.Label {
|
|
||||||
id: urlLabel
|
|
||||||
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Kirigami.ShadowedRectangle {
|
|
||||||
corners.topRightRadius: Kirigami.Units.cornerRadius
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
border {
|
|
||||||
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
|
|
||||||
width: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
@@ -54,15 +52,6 @@ ColumnLayout {
|
|||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.SelectableLabel {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
font: Kirigami.Theme.smallFont
|
|
||||||
textFormat: TextEdit.PlainText
|
|
||||||
visible: root.currentRoom && root.currentRoom.canonicalAlias
|
|
||||||
text: root.currentRoom && root.currentRoom.canonicalAlias ? root.currentRoom.canonicalAlias : ""
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
text: root.currentRoom.displayName
|
text: root.currentRoom.displayName
|
||||||
|
|
||||||
@@ -81,14 +70,7 @@ ColumnLayout {
|
|||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
text: root.invitingMember.displayName
|
text: root.currentRoom.displayName
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.Label {
|
|
||||||
text: root.invitingMember.id
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
}
|
}
|
||||||
@@ -177,7 +159,7 @@ ColumnLayout {
|
|||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
color: Kirigami.Theme.disabledTextColor
|
color: Kirigami.Theme.disabledTextColor
|
||||||
text: xi18nc("@info:label Ensure you are referring to the same translation used for that settings page", "You can reject invitations from unknown users under the <interface>Security & Safety</interface> settings.")
|
text: i18nc("@info:label", "You can reject invitations from unknown users under Security settings.")
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
// + 5 to prevent it from wrapping unnecessarily
|
// + 5 to prevent it from wrapping unnecessarily
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import QtQuick.Layouts
|
|||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.components as KirigamiComponents
|
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||||
import org.kde.kirigamiaddons.formcard as FormCard
|
import org.kde.kirigamiaddons.formcard as FormCard
|
||||||
|
import org.kde.prison
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ Kirigami.Dialog {
|
|||||||
|
|
||||||
standardButtons: Kirigami.Dialog.NoButton
|
standardButtons: Kirigami.Dialog.NoButton
|
||||||
|
|
||||||
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
|
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||||
title: i18nc("@title:dialog", "Join Room")
|
title: i18nc("@title:dialog", "Join Room")
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
@@ -66,7 +67,7 @@ Kirigami.Dialog {
|
|||||||
text: i18nc("@action:button", "Join room")
|
text: i18nc("@action:button", "Join room")
|
||||||
icon.name: "irc-join-channel"
|
icon.name: "irc-join-channel"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
RoomManager.resolveResource(root.room, "join_confirmed");
|
RoomManager.resolveResource(root.room, "join");
|
||||||
root.close();
|
root.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQuick.Window
|
|
||||||
import QtQml
|
import QtQml
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
@@ -51,22 +50,6 @@ Kirigami.Page {
|
|||||||
sourceComponent: message
|
sourceComponent: message
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
|
||||||
name: "waitingForKey"
|
|
||||||
when: root.session.state === KeyVerificationSession.WAITINGFORKEY
|
|
||||||
PropertyChanges {
|
|
||||||
target: stateLoader
|
|
||||||
sourceComponent: message
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "waitingForAccept"
|
|
||||||
when: root.session.state === KeyVerificationSession.WAITINGFORACCEPT
|
|
||||||
PropertyChanges {
|
|
||||||
target: stateLoader
|
|
||||||
sourceComponent: message
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
State {
|
||||||
name: "waitingForMac"
|
name: "waitingForMac"
|
||||||
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
|
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
|
||||||
@@ -144,9 +127,7 @@ Kirigami.Page {
|
|||||||
case KeyVerificationSession.WAITINGFORREADY:
|
case KeyVerificationSession.WAITINGFORREADY:
|
||||||
case KeyVerificationSession.INCOMING:
|
case KeyVerificationSession.INCOMING:
|
||||||
case KeyVerificationSession.WAITINGFORMAC:
|
case KeyVerificationSession.WAITINGFORMAC:
|
||||||
case KeyVerificationSession.WAITINGFORKEY:
|
return "security-medium-symbolic";
|
||||||
case KeyVerificationSession.WAITINGFORACCEPT:
|
|
||||||
return "security-medium-symbolic";
|
|
||||||
case KeyVerificationSession.DONE:
|
case KeyVerificationSession.DONE:
|
||||||
return "security-high";
|
return "security-high";
|
||||||
default:
|
default:
|
||||||
@@ -160,19 +141,13 @@ Kirigami.Page {
|
|||||||
case KeyVerificationSession.INCOMING:
|
case KeyVerificationSession.INCOMING:
|
||||||
return i18n("Incoming key verification request from device **%1**", root.session.remoteDeviceId);
|
return i18n("Incoming key verification request from device **%1**", root.session.remoteDeviceId);
|
||||||
case KeyVerificationSession.WAITINGFORMAC:
|
case KeyVerificationSession.WAITINGFORMAC:
|
||||||
return i18n("Waiting for other party to send us keys.");
|
|
||||||
case KeyVerificationSession.WAITINGFORKEY:
|
|
||||||
return i18n("Waiting for other party to confirm our keys.");
|
|
||||||
case KeyVerificationSession.WAITINGFORACCEPT:
|
|
||||||
return i18n("Waiting for other party to verify.");
|
return i18n("Waiting for other party to verify.");
|
||||||
case KeyVerificationSession.DONE:
|
case KeyVerificationSession.DONE:
|
||||||
return i18n("Successfully verified device **%1**", root.session.remoteDeviceId);
|
return i18n("Successfully verified device **%1**", root.session.remoteDeviceId)
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isDone: root.session.state === KeyVerificationSession.DONE
|
|
||||||
onDone: root.closeDialog()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.config as KConfig
|
import org.kde.config as KConfig
|
||||||
@@ -19,11 +20,6 @@ Kirigami.ApplicationWindow {
|
|||||||
|
|
||||||
property bool initialized: false
|
property bool initialized: false
|
||||||
|
|
||||||
readonly property QuickSwitcher quickSwitcher: QuickSwitcher {
|
|
||||||
connection: root.connection
|
|
||||||
window: root
|
|
||||||
}
|
|
||||||
|
|
||||||
title: {
|
title: {
|
||||||
if (NeoChatConfig.windowTitleFocus) {
|
if (NeoChatConfig.windowTitleFocus) {
|
||||||
return activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "");
|
return activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "");
|
||||||
@@ -87,7 +83,6 @@ Kirigami.ApplicationWindow {
|
|||||||
active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile
|
active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile
|
||||||
sourceComponent: GlobalMenu {
|
sourceComponent: GlobalMenu {
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
appWindow: root
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,12 +90,19 @@ Kirigami.ApplicationWindow {
|
|||||||
configGroupName: "MainWindow"
|
configGroupName: "MainWindow"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QuickSwitcher {
|
||||||
|
id: quickSwitcher
|
||||||
|
connection: root.connection
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: RoomManager
|
target: RoomManager
|
||||||
|
|
||||||
function onCurrentRoomChanged() {
|
function onCurrentRoomChanged() {
|
||||||
if (RoomManager.currentRoom && root.pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
|
if (RoomManager.currentRoom && pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
|
||||||
let roomPage = root.pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
|
let roomPage = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
|
||||||
|
connection: root.connection
|
||||||
|
});
|
||||||
roomPage.backRequested.connect(event => {
|
roomPage.backRequested.connect(event => {
|
||||||
RoomManager.clearCurrentRoom();
|
RoomManager.clearCurrentRoom();
|
||||||
});
|
});
|
||||||
@@ -108,26 +110,33 @@ Kirigami.ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onAskJoinRoom(room) {
|
function onAskJoinRoom(room) {
|
||||||
(Qt.createComponent("org.kde.neochat", "JoinRoomDialog").createObject(root, {
|
Qt.createComponent("org.kde.neochat", "JoinRoomDialog").createObject(root, {
|
||||||
room: room,
|
room: room,
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
}) as JoinRoomDialog).open();
|
}).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onShowUserDetail(user, room) {
|
function onShowUserDetail(user, room) {
|
||||||
root.showUserDetail(user, room);
|
root.showUserDetail(user, room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToEvent(event) {
|
||||||
|
if (event.length > 0) {
|
||||||
|
roomItem.goToEvent(event);
|
||||||
|
}
|
||||||
|
roomItem.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
function onAskDirectChatConfirmation(user) {
|
function onAskDirectChatConfirmation(user) {
|
||||||
(Qt.createComponent("org.kde.neochat", "AskDirectChatConfirmation").createObject(this, {
|
Qt.createComponent("org.kde.neochat", "AskDirectChatConfirmation").createObject(this, {
|
||||||
user: user
|
user: user
|
||||||
}) as AskDirectChatConfirmation).open();
|
}).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onExternalUrl(url) {
|
function onExternalUrl(url) {
|
||||||
(Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog").createObject(this, {
|
let dialog = Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog").createObject(this);
|
||||||
link: url
|
dialog.link = url;
|
||||||
}) as ConfirmUrlDialog).open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +200,7 @@ Kirigami.ApplicationWindow {
|
|||||||
dim = false;
|
dim = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enabled: RoomManager.hasOpenRoom && root.pageStack.layers.depth < 2 && root.pageStack.depth < 3 && (root.pageStack.visibleItems.length > 1 || root.pageStack.currentIndex > 0) && !Kirigami.Settings.isMobile && root.pageStack.wideMode
|
enabled: RoomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3 && (pageStack.visibleItems.length > 1 || pageStack.currentIndex > 0) && !Kirigami.Settings.isMobile && root.pageStack.wideMode
|
||||||
handleVisible: enabled
|
handleVisible: enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,10 +222,10 @@ Kirigami.ApplicationWindow {
|
|||||||
Connections {
|
Connections {
|
||||||
target: NeoChatConfig
|
target: NeoChatConfig
|
||||||
function onBlurChanged() {
|
function onBlurChanged() {
|
||||||
WindowController.setBlur(root.pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
|
WindowController.setBlur(pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
|
||||||
}
|
}
|
||||||
function onCompactLayoutChanged() {
|
function onCompactLayoutChanged() {
|
||||||
WindowController.setBlur(root.pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
|
WindowController.setBlur(pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +242,7 @@ Kirigami.ApplicationWindow {
|
|||||||
RoomListPage {
|
RoomListPage {
|
||||||
id: roomList
|
id: roomList
|
||||||
|
|
||||||
onSearch: root.quickSwitcher.open()
|
onSearch: quickSwitcher.open()
|
||||||
|
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
|
|
||||||
@@ -267,23 +276,12 @@ Kirigami.ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root.connection
|
|
||||||
function onLoggedOut(): void {
|
|
||||||
root.pageStack.clear();
|
|
||||||
let page = root.pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {
|
|
||||||
showExisting: true,
|
|
||||||
}) as WelcomePage;
|
|
||||||
page.connectionChosen.connect(() => root.load())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: AccountRegistry
|
target: AccountRegistry
|
||||||
function onRowsRemoved() {
|
function onRowsRemoved() {
|
||||||
if (AccountRegistry.rowCount() === 0) {
|
if (AccountRegistry.rowCount() === 0) {
|
||||||
root.pageStack.clear();
|
pageStack.clear();
|
||||||
root.pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'));
|
pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,7 +290,7 @@ Kirigami.ApplicationWindow {
|
|||||||
target: Controller
|
target: Controller
|
||||||
|
|
||||||
function onErrorOccured(error) {
|
function onErrorOccured(error) {
|
||||||
root.showPassiveNotification(error, "short");
|
showPassiveNotification(error, "short");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,9 +305,9 @@ Kirigami.ApplicationWindow {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function onUserConsentRequired(url) {
|
function onUserConsentRequired(url) {
|
||||||
(Qt.createComponent("org.kde.neochat", "ConsentDialog").createObject(this, {
|
Qt.createComponent("org.kde.neochat", "ConsentDialog").createObject(this, {
|
||||||
url: url
|
url: url
|
||||||
}) as ConsentDialog).open();
|
}).open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +338,7 @@ Kirigami.ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handleShare(): void {
|
function handleShare(): void {
|
||||||
const dialog = root.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
const dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
||||||
connection: root.connection
|
connection: root.connection
|
||||||
}, {
|
}, {
|
||||||
title: i18nc("@title", "Share"),
|
title: i18nc("@title", "Share"),
|
||||||
@@ -357,12 +355,8 @@ Kirigami.ApplicationWindow {
|
|||||||
room: room,
|
room: room,
|
||||||
user: user,
|
user: user,
|
||||||
connection: root.connection,
|
connection: root.connection,
|
||||||
}) as UserDetailDialog;
|
});
|
||||||
// FIXME: The reason why we don't want the focusedWindowItem for the room null case (aka QR codes) is because it will parent it to the soon-to-be-destroyed window item.
|
dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again
|
||||||
// But this won't be a problem if we turn it into a Kirigami.Dialog or some other in-scene item, which it really should be.
|
|
||||||
if (room != null) {
|
|
||||||
dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again
|
|
||||||
}
|
|
||||||
dialog.open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +365,9 @@ Kirigami.ApplicationWindow {
|
|||||||
RoomManager.loadInitialRoom();
|
RoomManager.loadInitialRoom();
|
||||||
|
|
||||||
if (!Kirigami.Settings.isMobile) {
|
if (!Kirigami.Settings.isMobile) {
|
||||||
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
|
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
|
||||||
|
connection: root.connection
|
||||||
|
});
|
||||||
roomPage.forceActiveFocus();
|
roomPage.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Kirigami.Dialog {
|
|||||||
/**
|
/**
|
||||||
* @brief Thrown when a user is selected.
|
* @brief Thrown when a user is selected.
|
||||||
*/
|
*/
|
||||||
signal userSelected(string userId)
|
signal userSelected
|
||||||
|
|
||||||
title: i18nc("@title", "User ID")
|
title: i18nc("@title", "User ID")
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ Kirigami.Dialog {
|
|||||||
text: i18n("OK")
|
text: i18n("OK")
|
||||||
icon.name: "dialog-ok"
|
icon.name: "dialog-ok"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
root.userSelected(userIdText.text)
|
root.connection.requestDirectChat(userIdText.text);
|
||||||
root.accept();
|
root.accept();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ Kirigami.Page {
|
|||||||
enabled: root.model
|
enabled: root.model
|
||||||
target: root.room
|
target: root.room
|
||||||
function onChanged(): void {
|
function onChanged(): void {
|
||||||
root.contentJson = root.model.stateEventContentJson(root.type, root.stateKey);
|
root.contentJson = model.stateEventContentJson(root.type, root.stateKey);
|
||||||
root.sourceText = root.model.stateEventJson(root.type, root.stateKey);
|
root.sourceText = model.stateEventJson(root.type, root.stateKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,8 +46,8 @@ Kirigami.Page {
|
|||||||
text: i18nc("@action As in 'edit the state of this room'", "Edit state")
|
text: i18nc("@action As in 'edit the state of this room'", "Edit state")
|
||||||
icon.name: "document-edit"
|
icon.name: "document-edit"
|
||||||
visible: root.allowEdit
|
visible: root.allowEdit
|
||||||
enabled: root.room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
|
enabled: room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
|
||||||
onTriggered: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog"), {
|
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog.qml"), {
|
||||||
room: root.room,
|
room: root.room,
|
||||||
type: root.type,
|
type: root.type,
|
||||||
stateKey: root.stateKey,
|
stateKey: root.stateKey,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user