Compare commits

..

86 Commits

Author SHA1 Message Date
l10n daemon script
c7c1c8fd5c GIT_SILENT Sync po/docbooks with svn 2025-10-06 03:29:02 +00:00
l10n daemon script
fdbee5a508 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2025-10-05 03:06:37 +00:00
Ron El
fb58003451 Bump the min required KF version to 6.16
This became required by de97275a38
because Kirigami.ColumnView.preferredWidth did not exist before then.

Reverting that commit enables this to still build with 6.13.
2025-10-03 10:29:32 +09:30
Heiko Becker
debbe8e478 GIT_SILENT Update Appstream for new release 2025-10-01 00:40:00 +02:00
Heiko Becker
0ce86e5a08 GIT_SILENT Upgrade release service version to 25.08.2. 2025-09-30 23:22:50 +02:00
l10n daemon script
6935d887c4 GIT_SILENT Sync po/docbooks with svn 2025-09-29 03:19:32 +00:00
l10n daemon script
963346e0f4 GIT_SILENT Sync po/docbooks with svn 2025-09-28 03:33:33 +00:00
l10n daemon script
e37dd88c43 GIT_SILENT Sync po/docbooks with svn 2025-09-27 03:18:49 +00:00
Joshua Goins
2aacb640c8 Fix some miscellaneous weirdness around push notifications
For some reason, I *stopped* the timer if we get a push message - which
makes *no* sense. It meant that NeoChat kept running in the background
for basically no reason.

We can also make the NotificationsManager::postPushNotification function
static and avoid creating some singletons.

(cherry picked from commit 94ea1305b2)
2025-09-26 15:49:45 -04:00
Joshua Goins
2d63a92702 Replace KDBusService usage with QDBusConnection when D-Bus activated
This is lighter and more reliable when it comes to being activated by
KUnifiedPush. We also don't need all of the features of KDBusService
here.

(cherry picked from commit 960377838d)
2025-09-26 15:49:45 -04:00
Joshua Goins
de6731cfda Remove useless translated string from dbus-activated CLI option
It never gets shown, so let's not waste our translators time.

(cherry picked from commit a48e8662d6)
2025-09-26 15:49:45 -04:00
l10n daemon script
7b3c40757c GIT_SILENT Sync po/docbooks with svn 2025-09-25 03:21:49 +00:00
l10n daemon script
87f243ba8b GIT_SILENT Sync po/docbooks with svn 2025-09-24 03:16:10 +00:00
l10n daemon script
192cdc1ff3 GIT_SILENT Sync po/docbooks with svn 2025-09-22 03:19:05 +00:00
l10n daemon script
c72f77f7b6 GIT_SILENT Sync po/docbooks with svn 2025-09-20 03:20:16 +00:00
Vlad Zahorodnii
d978f8de50 Fix inserting UserListModel items without beginInsertRows()
If an item is added, the corresponding code should be wrapped with
beginInsertRows() and endInsertRows(), otherwise proxy or filter models
can end up with corrupted internal state.

m_members.insert() in refreshMember() should be unnecessary because
if pos is not the same as m_members.size(), then it means there's already
a member.id() item in the member list.


(cherry picked from commit 161815acff)

Co-authored-by: Vlad Zahorodnii <vlad.zahorodnii@kde.org>
2025-09-15 18:15:56 +00:00
l10n daemon script
cf216268ab GIT_SILENT Sync po/docbooks with svn 2025-09-15 03:15:28 +00:00
l10n daemon script
f9741a66c4 GIT_SILENT Sync po/docbooks with svn 2025-09-14 03:12:48 +00:00
l10n daemon script
aac3bfda88 GIT_SILENT Sync po/docbooks with svn 2025-09-12 03:13:06 +00:00
l10n daemon script
2722a6f2f0 GIT_SILENT Sync po/docbooks with svn 2025-09-10 03:14:06 +00:00
Joshua Goins
ecf4b85f00 Send thumbnails when uploading videos
NeoChat will now generate thumbnails from the first frame of the video,
since we are loading it anyway. This makes the experience of exchanging
videos in NeoChat much nicer!


(cherry picked from commit a0b3e484f5)

Co-authored-by: Joshua Goins <josh@redstrate.com>
2025-09-09 08:51:41 -04:00
Tobias Fella
ec1413d1ce Set object ownership for NeoChatRoomMembers
(cherry picked from commit 4c638a740e)

Co-authored-by: Tobias Fella <tobias.fella@kde.org>
2025-09-09 08:15:50 +00:00
l10n daemon script
0b7a6df0a3 GIT_SILENT Sync po/docbooks with svn 2025-09-09 04:29:46 +00:00
l10n daemon script
040efa46f9 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2025-09-09 04:19:53 +00:00
l10n daemon script
ef4b41e6f8 GIT_SILENT made messages (after extraction) 2025-09-09 03:28:55 +00:00
l10n daemon script
80f81847f4 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2025-09-08 04:03:30 +00:00
Heiko Becker
681a3c4036 GIT_SILENT Update Appstream for new release 2025-09-04 01:31:12 +02:00
Heiko Becker
fd27c70b85 GIT_SILENT Upgrade release service version to 25.08.1. 2025-09-04 00:09:03 +02:00
l10n daemon script
435124ffe8 GIT_SILENT Sync po/docbooks with svn 2025-09-03 03:26:05 +00:00
l10n daemon script
e2ca698389 GIT_SILENT Sync po/docbooks with svn 2025-09-02 03:42:16 +00:00
l10n daemon script
7e9cfbedc9 GIT_SILENT Sync po/docbooks with svn 2025-09-01 03:24:20 +00:00
l10n daemon script
eeed8a7277 GIT_SILENT Sync po/docbooks with svn 2025-08-27 03:27:03 +00:00
l10n daemon script
aa8c515432 GIT_SILENT Sync po/docbooks with svn 2025-08-26 03:17:13 +00:00
l10n daemon script
ba30014d40 GIT_SILENT Sync po/docbooks with svn 2025-08-23 03:21:47 +00:00
l10n daemon script
d131030d47 GIT_SILENT made messages (after extraction) 2025-08-22 02:36:59 +00:00
l10n daemon script
23a0d91627 GIT_SILENT Sync po/docbooks with svn 2025-08-21 03:29:16 +00:00
l10n daemon script
11d5a37ffe GIT_SILENT Sync po/docbooks with svn 2025-08-20 03:17:17 +00:00
l10n daemon script
256a8e5a1e GIT_SILENT Sync po/docbooks with svn 2025-08-16 03:21:53 +00:00
l10n daemon script
b608921d43 GIT_SILENT Sync po/docbooks with svn 2025-08-15 03:47:04 +00:00
l10n daemon script
45a3984bf9 GIT_SILENT Sync po/docbooks with svn 2025-08-14 03:34:47 +00:00
l10n daemon script
570e0425e9 GIT_SILENT Sync po/docbooks with svn 2025-08-13 03:19:34 +00:00
Tobias Fella
09ed1bd616 Fix copying images
(cherry picked from commit ef4f11546f)
2025-08-12 16:36:03 +02:00
Tobias Fella
3201426886 Update room versions in security settings
We need to come up with a better way of testing the versions here, but that's for different patch

(cherry picked from commit 35b363fdce)
2025-08-11 23:16:43 +02:00
l10n daemon script
96dc83d807 GIT_SILENT made messages (after extraction) 2025-08-11 02:40:56 +00:00
l10n daemon script
ab1fb8ae97 GIT_SILENT Sync po/docbooks with svn 2025-08-10 03:27:35 +00:00
l10n daemon script
c5a4b2a50a GIT_SILENT Sync po/docbooks with svn 2025-08-07 21:08:05 +00:00
l10n daemon script
17fdad3afd GIT_SILENT made messages (after extraction) 2025-08-07 20:25:41 +00:00
l10n daemon script
64bc2691cb GIT_SILENT Sync po/docbooks with svn 2025-08-07 03:23:41 +00:00
l10n daemon script
c7df34a9c8 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2025-08-07 03:19:05 +00:00
l10n daemon script
1525c74b10 GIT_SILENT made messages (after extraction) 2025-08-07 02:40:41 +00:00
l10n daemon script
58b85622c5 GIT_SILENT Sync po/docbooks with svn 2025-08-06 03:35:41 +00:00
l10n daemon script
796470d0e0 GIT_SILENT Sync po/docbooks with svn 2025-08-05 03:55:46 +00:00
l10n daemon script
3a43d99575 GIT_SILENT Sync po/docbooks with svn 2025-08-04 03:40:01 +00:00
l10n daemon script
a62798ef1e GIT_SILENT Sync po/docbooks with svn 2025-08-03 03:19:30 +00:00
l10n daemon script
349d0c5f5f GIT_SILENT Sync po/docbooks with svn 2025-08-02 03:15:57 +00:00
Tobias Fella
c917fc0166 Fix account switching on logout
(cherry picked from commit 501f14fead)
2025-08-01 12:28:35 +02:00
Tobias Fella
a4f2d8fca1 Fix loading
(cherry picked from commit d14466451d)
2025-08-01 12:13:38 +02:00
Tobias Fella
fd18f88adf Fix common crash during login
(cherry picked from commit 7742c6d4b0)
2025-08-01 11:28:43 +02:00
l10n daemon script
546694b08e GIT_SILENT Sync po/docbooks with svn 2025-08-01 03:27:30 +00:00
Heiko Becker
699026fc2f GIT_SILENT Update Appstream for new release 2025-08-01 00:54:04 +02:00
Heiko Becker
c61c2d7437 GIT_SILENT Upgrade release service version to 25.08.0. 2025-07-31 23:43:12 +02:00
l10n daemon script
56babbc1c5 GIT_SILENT Sync po/docbooks with svn 2025-07-31 03:14:23 +00:00
Joshua Goins
74b3e703c1 Replace duplicate beginResetModel with endResetModel
The initialize method was calling beginResetModel twice without
a corresponding endResetModel call. This could cause model state
inconsistencies.


(cherry picked from commit 4e0b295f66)

Co-authored-by: Wang Yu <wangyu@uniontech.com>
2025-07-30 21:13:01 -04:00
l10n daemon script
f620221113 GIT_SILENT Sync po/docbooks with svn 2025-07-30 03:15:18 +00:00
Tobias Fella
896c001430 Fix test
(cherry picked from commit 24e43d063a)
2025-07-29 17:04:21 +02:00
l10n daemon script
defee77c96 GIT_SILENT Sync po/docbooks with svn 2025-07-29 03:17:38 +00:00
l10n daemon script
4328ab8e89 GIT_SILENT Sync po/docbooks with svn 2025-07-28 03:13:37 +00:00
Tobias Fella
54b081abba Prepare for new RoomId format
See MSC4291

(cherry picked from commit edf5d55da4)
2025-07-27 21:54:13 +02:00
l10n daemon script
29686608e1 GIT_SILENT Sync po/docbooks with svn 2025-07-25 03:20:27 +00:00
Heiko Becker
b720ecf29d GIT_SILENT Upgrade release service version to 25.07.90. 2025-07-23 22:24:21 +02:00
l10n daemon script
fc859d679a GIT_SILENT Sync po/docbooks with svn 2025-07-23 03:18:09 +00:00
l10n daemon script
3595ad9293 GIT_SILENT Sync po/docbooks with svn 2025-07-22 03:21:18 +00:00
l10n daemon script
73f8ebc54e GIT_SILENT Sync po/docbooks with svn 2025-07-21 03:18:12 +00:00
l10n daemon script
19cf534acd GIT_SILENT Sync po/docbooks with svn 2025-07-17 03:14:25 +00:00
l10n daemon script
9b86088e26 GIT_SILENT Sync po/docbooks with svn 2025-07-16 04:06:03 +00:00
l10n daemon script
a93117fcd6 GIT_SILENT Sync po/docbooks with svn 2025-07-15 03:24:36 +00:00
l10n daemon script
ee20c90498 GIT_SILENT Sync po/docbooks with svn 2025-07-14 03:37:26 +00:00
l10n daemon script
860a2267d5 GIT_SILENT Sync po/docbooks with svn 2025-07-13 03:15:50 +00:00
l10n daemon script
cb9b2648ca GIT_SILENT Sync po/docbooks with svn 2025-07-12 03:21:34 +00:00
l10n daemon script
1e798b6c15 GIT_SILENT Sync po/docbooks with svn 2025-07-11 03:17:16 +00:00
l10n daemon script
124ffba5e0 GIT_SILENT Sync po/docbooks with svn 2025-07-10 03:16:20 +00:00
l10n daemon script
3aaaa610df GIT_SILENT Sync po/docbooks with svn 2025-07-09 03:22:43 +00:00
l10n daemon script
18e883834c GIT_SILENT Sync po/docbooks with svn 2025-07-08 03:24:01 +00:00
l10n daemon script
6acbd2dffd GIT_SILENT Sync po/docbooks with svn 2025-07-07 03:14:00 +00:00
l10n daemon script
dc5c27aa2d GIT_SILENT Sync po/docbooks with svn 2025-07-06 03:22:47 +00:00
Albert Astals Cid
26774bbe56 GIT_SILENT Upgrade release service version to 25.07.80. 2025-07-05 11:30:42 +02:00
183 changed files with 30963 additions and 39488 deletions

View File

@@ -1,2 +0,0 @@
[General]
disableUnqualifiedAccess = "i18nc,xi18nc,i18ncp"

View File

@@ -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",
@@ -90,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",
@@ -164,13 +156,13 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://download.kde.org/stable/release-service/25.04.3/src/kunifiedpush-25.04.3.tar.xz", "url": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-1.0.0.tar.xz",
"sha256": "a16ffe4117b14baa02f3b8ae7de9e509a17359c1b67dcd851aef4f3c3661a1df", "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"
} }
} }
] ]

View File

@@ -8,8 +8,8 @@ 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 "2")
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})

View File

@@ -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"

View File

@@ -92,9 +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(
roommanagertest.cpp
LINK_LIBRARIES neochat Qt::Test neochat_server
TEST_NAME roommanagertest
)

View File

@@ -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()

View File

@@ -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"
}
}
]
}
}

View File

@@ -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,13 +39,13 @@ 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);

View File

@@ -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()

View File

@@ -1,132 +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 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);
}
QTEST_MAIN(RoomManagerTest)
#include "roommanagertest.moc"

View File

@@ -115,36 +115,28 @@ void Server::start()
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) { m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) {
QMap<QString, QJsonArray> stateEvents; QMap<QString, QJsonArray> stateEvents;
QMap<QString, QJsonArray> roomAccountData;
for (const auto &roomData : m_roomsToCreate) { for (const auto &[roomId, matrixId] : m_roomsToCreate) {
stateEvents[roomData.id] += QJsonObject{ stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}}, {u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
{u"event_id"_s, generateEventId()}, {u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()}, {u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomData.id}, {u"room_id"_s, roomId},
{u"sender"_s, roomData.members[0]}, {u"sender"_s, matrixId},
{u"state_key"_s, QString()}, {u"state_key"_s, QString()},
{u"type"_s, u"m.room.create"_s}, {u"type"_s, u"m.room.create"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}}, {u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
}; };
for (const auto &member : roomData.members) { stateEvents[roomId] += QJsonObject{
stateEvents[roomData.id] += QJsonObject{ {u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}}, {u"event_id"_s, generateEventId()},
{u"event_id"_s, generateEventId()}, {u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()}, {u"room_id"_s, roomId},
{u"room_id"_s, roomData.id}, {u"sender"_s, matrixId},
{u"sender"_s, member}, {u"state_key"_s, matrixId},
{u"state_key"_s, member}, {u"type"_s, u"m.room.member"_s},
{u"type"_s, u"m.room.member"_s}, {u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}}, };
};
}
QJsonObject tags;
for (const auto &tag : roomData.tags) {
tags[tag] = QJsonObject();
}
roomAccountData[roomData.id] += QJsonObject{{u"type"_s, u"m.tag"_s}, {u"content"_s, QJsonObject{{u"tags"_s, tags}}}};
} }
m_roomsToCreate.clear(); m_roomsToCreate.clear();
for (const auto &roomId : m_invitedUsers.keys()) { for (const auto &roomId : m_invitedUsers.keys()) {
@@ -199,18 +191,11 @@ void Server::start()
m_joinedUsers.clear(); m_joinedUsers.clear();
QJsonObject rooms; QJsonObject rooms;
auto keys = stateEvents.keys() + m_events.keys(); for (const auto &roomId : stateEvents.keys()) {
for (const auto &roomId : QSet(keys.begin(), keys.end())) { rooms[roomId] = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}}};
rooms[roomId] = QJsonObject{
{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}},
{u"account_data"_s, QJsonObject{{u"events"_s, roomAccountData[roomId]}}},
{u"timeline"_s, QJsonObject{{u"events"_s, m_events[roomId]}}},
};
} }
m_events.clear();
auto json = QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}}; responder.write(QJsonDocument(QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}}), QHttpServerResponder::StatusCode::Ok);
responder.write(QJsonDocument(json), QHttpServerResponder::StatusCode::Ok);
}); });
QSslConfiguration config; QSslConfiguration config;
@@ -230,11 +215,7 @@ void Server::start()
QString Server::createRoom(const QString &matrixId) QString Server::createRoom(const QString &matrixId)
{ {
auto roomId = generateRoomId(); auto roomId = generateRoomId();
m_roomsToCreate += RoomData{ m_roomsToCreate += {roomId, matrixId};
.members = {matrixId},
.id = roomId,
.tags = {},
};
return roomId; return roomId;
} }
@@ -252,23 +233,3 @@ void Server::joinUser(const QString &roomId, const QString &matrixId)
{ {
m_joinedUsers[roomId] += matrixId; m_joinedUsers[roomId] += matrixId;
} }
QString Server::createServerNoticesRoom(const QString &matrixId)
{
auto roomId = createRoom(matrixId);
m_roomsToCreate.last().tags = {u"m.server_notice"_s};
return roomId;
}
QString Server::sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content)
{
const auto eventId = generateEventId();
m_events[roomId] += 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()},
};
return eventId;
}

View File

@@ -4,12 +4,6 @@
#include <QHttpServer> #include <QHttpServer>
#include <QSslServer> #include <QSslServer>
struct RoomData {
QStringList members;
QString id;
QStringList tags;
};
class Server class Server
{ {
public: public:
@@ -27,12 +21,6 @@ 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;
@@ -41,6 +29,5 @@ private:
QHash<QString, QList<QString>> m_bannedUsers; QHash<QString, QList<QString>> m_bannedUsers;
QHash<QString, QList<QString>> m_joinedUsers; QHash<QString, QList<QString>> m_joinedUsers;
QList<RoomData> m_roomsToCreate; QList<std::pair<QString, QString>> m_roomsToCreate;
QMap<QString, QJsonArray> m_events;
}; };

View File

@@ -49,7 +49,6 @@
<name xml:lang="ta">நியோச்சாட்</name> <name xml:lang="ta">நியோச்சாட்</name>
<name xml:lang="tr">NeoChat</name> <name xml:lang="tr">NeoChat</name>
<name xml:lang="uk">NeoChat</name> <name xml:lang="uk">NeoChat</name>
<name xml:lang="x-test">xxNeoChatxx</name>
<name xml:lang="zh-CN">NeoChat</name> <name xml:lang="zh-CN">NeoChat</name>
<name xml:lang="zh-TW">NeoChat</name> <name xml:lang="zh-TW">NeoChat</name>
<summary>Chat on Matrix</summary> <summary>Chat on Matrix</summary>
@@ -83,7 +82,6 @@
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary> <summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary> <summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
<summary xml:lang="uk">Спілкування у Matrix</summary> <summary xml:lang="uk">Спілкування у Matrix</summary>
<summary xml:lang="x-test">xxChat on Matrixxx</summary>
<summary xml:lang="zh-TW">在 Matrix 上聊天</summary> <summary xml:lang="zh-TW">在 Matrix 上聊天</summary>
<description> <description>
<p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p> <p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
@@ -117,10 +115,9 @@
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p> <p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
<p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p> <p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p>
<p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p> <p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p>
<p xml:lang="x-test">xxNeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.xx</p>
<p xml:lang="zh-TW">NeoChat 是一個讓您能夠完全利用 Matrix 網路的聊天應用程式。它讓您安全地傳送文字訊息、影片或音訊檔給家人、同事或朋友等等。</p> <p xml:lang="zh-TW">NeoChat 是一個讓您能夠完全利用 Matrix 網路的聊天應用程式。它讓您安全地傳送文字訊息、影片或音訊檔給家人、同事或朋友等等。</p>
<p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p> <p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. يوفر نيوتشات كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP و تعدد الخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p> <p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. يوفر نيوتشات كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP و تعدد الخيوط وبعض جوانب التعمية من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار، ولكن يبقى الهدف توفير تطبيق للمواصفات بأكملها.</p>
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p> <p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p> <p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
<p xml:lang="de">NeoChat versucht eine vollumfängliche Anwendung für die Spezifikation von Matrix zu sein. Damit wird alles der aktuellen stabilen Spezifikation mit den erwähnenswerten Ausnahmen von VoIP, Diskussionsfäden und ein paar Teilen der Ende-zu-Ende-Verschlüsselung unterstützt. Zudem sind andere kleinere Auslassungen vorhanden, da sich die Matrixspezifikation ständig weiterentwickelt. Nichtsdestotrotz soll letztendlich die gesamte Spezifikation unterstützt werden.</p> <p xml:lang="de">NeoChat versucht eine vollumfängliche Anwendung für die Spezifikation von Matrix zu sein. Damit wird alles der aktuellen stabilen Spezifikation mit den erwähnenswerten Ausnahmen von VoIP, Diskussionsfäden und ein paar Teilen der Ende-zu-Ende-Verschlüsselung unterstützt. Zudem sind andere kleinere Auslassungen vorhanden, da sich die Matrixspezifikation ständig weiterentwickelt. Nichtsdestotrotz soll letztendlich die gesamte Spezifikation unterstützt werden.</p>
@@ -151,10 +148,9 @@
<p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p> <p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifrelemenin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p> <p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifrelemenin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p> <p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.xx</p>
<p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p> <p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p>
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p> <p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p> <p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يوفر نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p> <p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
<p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p> <p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
<p xml:lang="de">Durch die Weiterentwicklung der Matrix-Spezifikation unterstützt auch NeoChat einige als noch instabil gekennzeichnete Funktionen. Derzeit sind das:</p> <p xml:lang="de">Durch die Weiterentwicklung der Matrix-Spezifikation unterstützt auch NeoChat einige als noch instabil gekennzeichnete Funktionen. Derzeit sind das:</p>
@@ -186,7 +182,6 @@
<p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் வித‍த்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p> <p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் வித‍த்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p> <p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p> <p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
<p xml:lang="zh-TW">由於 Matrix 標準的開發流程的緣故NeoChat 也支援數個非穩定版的功能。目前這些功能是:</p> <p xml:lang="zh-TW">由於 Matrix 標準的開發流程的緣故NeoChat 也支援數個非穩定版的功能。目前這些功能是:</p>
<ul> <ul>
<li>Polls - MSC3381</li> <li>Polls - MSC3381</li>
@@ -221,7 +216,6 @@
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li> <li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
<li xml:lang="tr">Anketler — MSC3381</li> <li xml:lang="tr">Anketler — MSC3381</li>
<li xml:lang="uk">Опитування - MSC3381</li> <li xml:lang="uk">Опитування - MSC3381</li>
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
<li xml:lang="zh-TW">投票 - MSC3381</li> <li xml:lang="zh-TW">投票 - MSC3381</li>
<li>Sticker Packs - MSC2545</li> <li>Sticker Packs - MSC2545</li>
<li xml:lang="ar">حزم الملصقات - MSC2545</li> <li xml:lang="ar">حزم الملصقات - MSC2545</li>
@@ -255,7 +249,6 @@
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li> <li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
<li xml:lang="tr">Çıkartma Paketleri — MSC2545</li> <li xml:lang="tr">Çıkartma Paketleri — MSC2545</li>
<li xml:lang="uk">Пакунки наліпок - MSC2545</li> <li xml:lang="uk">Пакунки наліпок - MSC2545</li>
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
<li xml:lang="zh-TW">貼圖包 - MSC2545</li> <li xml:lang="zh-TW">貼圖包 - MSC2545</li>
<li>Location Events - MSC3488</li> <li>Location Events - MSC3488</li>
<li xml:lang="ar">موقع الأحداث - MSC3488</li> <li xml:lang="ar">موقع الأحداث - MSC3488</li>
@@ -289,7 +282,6 @@
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li> <li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
<li xml:lang="tr">Konum Etkinlikleri — MSC3488</li> <li xml:lang="tr">Konum Etkinlikleri — MSC3488</li>
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li> <li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
<li xml:lang="zh-TW">位置事件 - MSC3488</li> <li xml:lang="zh-TW">位置事件 - MSC3488</li>
</ul> </ul>
</description> </description>
@@ -306,8 +298,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>
@@ -359,7 +351,6 @@
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption> <caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption> <caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption> <caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption> <caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot> </screenshot>
<screenshot type="default"> <screenshot type="default">
@@ -396,7 +387,6 @@
<caption xml:lang="ta">மேட்ரிக்ஸு இடங்களின் மூலம் புதிய சமூகங்களைக் கண்டுபிடிக்கலாம்</caption> <caption xml:lang="ta">மேட்ரிக்ஸு இடங்களின் மூலம் புதிய சமூகங்களைக் கண்டுபிடிக்கலாம்</caption>
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption> <caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption>
<caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption> <caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption>
<caption xml:lang="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
<caption xml:lang="zh-TW">利用 Matrix 聊天空間發現新的社群</caption> <caption xml:lang="zh-TW">利用 Matrix 聊天空間發現新的社群</caption>
</screenshot> </screenshot>
<!-- <!--
@@ -441,7 +431,6 @@
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption> <caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption> <caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption> <caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption> <caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot> </screenshot>
<screenshot environment="windows"> <screenshot environment="windows">
@@ -480,7 +469,6 @@
<caption xml:lang="ta">நுழைவுத் திரை</caption> <caption xml:lang="ta">நுழைவுத் திரை</caption>
<caption xml:lang="tr">Oturum açma ekranı</caption> <caption xml:lang="tr">Oturum açma ekranı</caption>
<caption xml:lang="uk">Вікно входу</caption> <caption xml:lang="uk">Вікно входу</caption>
<caption xml:lang="x-test">xxLogin screenxx</caption>
<caption xml:lang="zh-TW">登入畫面</caption> <caption xml:lang="zh-TW">登入畫面</caption>
</screenshot> </screenshot>
</screenshots> </screenshots>
@@ -488,6 +476,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.2" date="2025-10-09"/>
<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"/>
<release version="25.04.2" date="2025-06-05"/> <release version="25.04.2" date="2025-06-05"/>

View File

@@ -44,7 +44,6 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட் Name[ta]=நியோச்சாட்
Name[tr]=NeoChat Name[tr]=NeoChat
Name[uk]=NeoChat Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat Name[zh_TW]=NeoChat
GenericName=Matrix Client GenericName=Matrix Client
@@ -88,7 +87,6 @@ GenericName[sv]=Matrix-klient
GenericName[ta]=Matrix வாங்கி GenericName[ta]=Matrix வாங்கி
GenericName[tr]=Matrix İstemcisi GenericName[tr]=Matrix İstemcisi
GenericName[uk]=Клієнт Matrix GenericName[uk]=Клієнт Matrix
GenericName[x-test]=xxMatrix Clientxx
GenericName[zh_CN]=Matrix 客户端 GenericName[zh_CN]=Matrix 客户端
GenericName[zh_TW]=Matrix 用戶端 GenericName[zh_TW]=Matrix 用戶端
Comment=Chat on Matrix Comment=Chat on Matrix
@@ -121,7 +119,6 @@ Comment[sv]=Chatta på Matrix
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும் Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
Comment[tr]=Matrix üzerinde sohbet edin Comment[tr]=Matrix üzerinde sohbet edin
Comment[uk]=Спілкування у Matrix Comment[uk]=Спілкування у Matrix
Comment[x-test]=xxChat on Matrixxx
Comment[zh_CN]=在 Matrix 上聊天 Comment[zh_CN]=在 Matrix 上聊天
Comment[zh_TW]=在 Matrix 上聊天 Comment[zh_TW]=在 Matrix 上聊天
MimeType=x-scheme-handler/matrix; MimeType=x-scheme-handler/matrix;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -104,7 +104,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
DEPENDENCIES DEPENDENCIES
QtCore QtCore
QtQuick QtQuick
com.github.quotient_im.libquotient
IMPORTS IMPORTS
org.kde.neochat.libneochat org.kde.neochat.libneochat
org.kde.neochat.rooms org.kde.neochat.rooms
@@ -116,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.purpose
org.kde.syntaxhighlighting
) )
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
@@ -341,6 +338,10 @@ 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_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
target_link_libraries(neochat PUBLIC KUnifiedPush) target_link_libraries(neochat PUBLIC KUnifiedPush)

View File

@@ -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,6 +14,7 @@
#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>
@@ -22,6 +24,7 @@
#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"
@@ -315,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.

View File

@@ -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.

View File

@@ -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)

View File

@@ -42,7 +42,6 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட் Name[ta]=நியோச்சாட்
Name[tr]=NeoChat Name[tr]=NeoChat
Name[uk]=NeoChat Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat Name[zh_TW]=NeoChat
DesktopEntry=org.kde.neochat DesktopEntry=org.kde.neochat
@@ -87,7 +86,6 @@ Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokolle
Comment[ta]=மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி Comment[ta]=மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி
Comment[tr]=Merkezi olmayan iletişim protokolü Matrix için bir istemci Comment[tr]=Merkezi olmayan iletişim protokolü Matrix için bir istemci
Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними
Comment[x-test]=xxA client for matrix, the decentralized communication protocolxx
Comment[zh_CN]=分布式通讯协议 Matrix 的客户端 Comment[zh_CN]=分布式通讯协议 Matrix 的客户端
Comment[zh_TW]=去中心化通訊協定 Matrix 的用戶端 Comment[zh_TW]=去中心化通訊協定 Matrix 的用戶端
@@ -134,7 +132,6 @@ Name[sv]=Nytt meddelande
Name[ta]=புதிய செய்தி Name[ta]=புதிய செய்தி
Name[tr]=Yeni İleti Name[tr]=Yeni İleti
Name[uk]=Нове повідомлення Name[uk]=Нове повідомлення
Name[x-test]=xxNew messagexx
Name[zh_CN]=新消息 Name[zh_CN]=新消息
Name[zh_TW]=新訊息 Name[zh_TW]=新訊息
Comment=There is a new message Comment=There is a new message
@@ -177,7 +174,6 @@ Comment[sv]=Det finns ett nytt meddelande
Comment[ta]=ஒரு புதிய செய்தி உள்ளது Comment[ta]=ஒரு புதிய செய்தி உள்ளது
Comment[tr]=Yeni bir ileti var Comment[tr]=Yeni bir ileti var
Comment[uk]=Надійшло нове повідомлення Comment[uk]=Надійшло нове повідомлення
Comment[x-test]=xxThere is a new messagexx
Comment[zh_CN]=有新消息 Comment[zh_CN]=有新消息
Comment[zh_TW]=有新的訊息 Comment[zh_TW]=有新的訊息
Action=Popup Action=Popup
@@ -222,7 +218,6 @@ Name[sv]=Ny inbjudan
Name[ta]=புதிய அழைப்பிதழ் Name[ta]=புதிய அழைப்பிதழ்
Name[tr]=Yeni Davet Name[tr]=Yeni Davet
Name[uk]=Нове запрошення Name[uk]=Нове запрошення
Name[x-test]=xxNew Invitationxx
Name[zh_CN]=新邀请 Name[zh_CN]=新邀请
Name[zh_TW]=新邀請 Name[zh_TW]=新邀請
Comment=There is a new invitation to a room Comment=There is a new invitation to a room
@@ -264,7 +259,6 @@ Comment[sv]=Det finns en ny inbjudan till ett rum
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
Comment[tr]=Bir odaya yeni bir davetiye var Comment[tr]=Bir odaya yeni bir davetiye var
Comment[uk]=У кімнаті нове запрошення Comment[uk]=У кімнаті нове запрошення
Comment[x-test]=xxThere is a new invitation to a roomxx
Comment[zh_CN]=有新的聊天室邀请 Comment[zh_CN]=有新的聊天室邀请
Comment[zh_TW]=有新的加入聊天室邀請 Comment[zh_TW]=有新的加入聊天室邀請
Action=Popup Action=Popup
@@ -303,7 +297,6 @@ Name[sv]=Dela
Name[ta]=பகிர் Name[ta]=பகிர்
Name[tr]=Paylaş Name[tr]=Paylaş
Name[uk]=Оприлюднення Name[uk]=Оприлюднення
Name[x-test]=xxSharexx
Name[zh_CN]=分享 Name[zh_CN]=分享
Name[zh_TW]=分享 Name[zh_TW]=分享
Comment=The result of sharing a piece of content Comment=The result of sharing a piece of content
@@ -338,7 +331,6 @@ Comment[sv]=Resultatet av att dela innehåll
Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவு Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவு
Comment[tr]=Bir parça içerik paylaşımının sonucu Comment[tr]=Bir parça içerik paylaşımının sonucu
Comment[uk]=Результат оприлюднення даних Comment[uk]=Результат оприлюднення даних
Comment[x-test]=xxThe result of sharing a piece of contentxx
Comment[zh_CN]=分享一个内容得到的结果 Comment[zh_CN]=分享一个内容得到的结果
Comment[zh_TW]=分享一份內容之後的結果 Comment[zh_TW]=分享一份內容之後的結果
Action=Popup Action=Popup

View File

@@ -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>

View File

@@ -389,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());
@@ -403,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;
} }
@@ -433,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);

View File

@@ -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.

View File

@@ -43,7 +43,6 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட் Name[ta]=நியோச்சாட்
Name[tr]=NeoChat Name[tr]=NeoChat
Name[uk]=NeoChat Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat Name[zh_TW]=NeoChat
Comment=Find rooms in NeoChat Comment=Find rooms in NeoChat
@@ -83,7 +82,6 @@ Comment[sv]=Sök efter rum i NeoChat
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும் Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
Comment[tr]=NeoChatte odalar bulun Comment[tr]=NeoChatte odalar bulun
Comment[uk]=Пошук кімнат у NeoChat Comment[uk]=Пошук кімнат у NeoChat
Comment[x-test]=xxFind rooms in NeoChatxx
Comment[zh_CN]=在 NeoChat 查找聊天室 Comment[zh_CN]=在 NeoChat 查找聊天室
Comment[zh_TW]=在 NeoChat 尋找聊天室 Comment[zh_TW]=在 NeoChat 尋找聊天室
X-KDE-ServiceTypes=Plasma/Runner X-KDE-ServiceTypes=Plasma/Runner
@@ -93,3 +91,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

View File

@@ -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
} }
} }

View File

@@ -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).clicked() Keys.onEnterPressed: accountView.currentItem.clicked()
Keys.onReturnPressed: (accountView.currentItem as Delegates.RoundedItemDelegate).clicked() Keys.onReturnPressed: accountView.currentItem.clicked()
onVisibleChanged: { onVisibleChanged: {
for (let i = 0; i < accountView.count; i++) { for (let i = 0; i < accountView.count; i++) {

View File

@@ -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

View File

@@ -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
} }
} }
} }

View File

@@ -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

View File

@@ -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
View 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();
}
}
}

View File

@@ -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"))
} }
} }
} }

View File

@@ -52,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
@@ -79,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
} }
@@ -175,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

View File

@@ -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 {

View File

@@ -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);
} }
} }
@@ -271,8 +280,8 @@ Kirigami.ApplicationWindow {
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'));
} }
} }
} }
@@ -281,7 +290,7 @@ Kirigami.ApplicationWindow {
target: Controller target: Controller
function onErrorOccured(error) { function onErrorOccured(error) {
root.showPassiveNotification(error, "short"); showPassiveNotification(error, "short");
} }
} }
@@ -296,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();
} }
} }
@@ -329,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"),
@@ -346,7 +355,7 @@ Kirigami.ApplicationWindow {
room: room, room: room,
user: user, user: user,
connection: root.connection, connection: root.connection,
}) as UserDetailDialog; });
dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again
dialog.open(); dialog.open();
} }
@@ -356,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();
} }

View File

@@ -47,7 +47,7 @@ Kirigami.Page {
icon.name: "document-edit" icon.name: "document-edit"
visible: root.allowEdit visible: root.allowEdit
enabled: 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: pageStack.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,

View File

@@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com> // SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL // 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 QtQuick.Layouts
@@ -10,8 +8,11 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kirigamiaddons.delegates as Delegates import org.kde.kirigamiaddons.delegates as Delegates
import Quotient
import org.kde.neochat import org.kde.neochat
Kirigami.Dialog { Kirigami.Dialog {
@@ -33,7 +34,7 @@ Kirigami.Dialog {
} }
] ]
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24) width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title: create new poll in the room", "Create Poll") title: i18nc("@title: create new poll in the room", "Create Poll")
contentItem: ColumnLayout { contentItem: ColumnLayout {
@@ -42,22 +43,22 @@ Kirigami.Dialog {
FormCard.FormComboBoxDelegate { FormCard.FormComboBoxDelegate {
id: pollTypeCombo id: pollTypeCombo
text: i18nc("@label", "Poll type:") text: i18n("Poll type:")
currentIndex: 0 currentIndex: 0
textRole: "text" textRole: "text"
valueRole: "value" valueRole: "value"
model: [ model: [
{ value: PollKind.Disclosed, text: i18nc("@item:inlistbox", "Open poll") }, { value: PollKind.Disclosed, text: i18n("Open poll") },
{ value: PollKind.Undisclosed, text: i18nc("@item:inlistbox", "Closed poll") } { value: PollKind.Undisclosed, text: i18n("Closed poll") }
] ]
} }
FormCard.FormTextDelegate { FormCard.FormTextDelegate {
verticalPadding: 0 verticalPadding: 0
text: pollTypeCombo.currentValue == 0 ? i18nc("@info", "Voters can see the result as soon as they have voted") : i18nc("@info", "Results are revealed only after the poll has closed") text: pollTypeCombo.currentValue == 0 ? i18n("Voters can see the result as soon as they have voted") : i18n("Results are revealed only after the poll has closed")
} }
FormCard.FormTextFieldDelegate { FormCard.FormTextFieldDelegate {
id: questionTextField id: questionTextField
label: i18nc("@label", "Question:") label: i18n("Question:")
} }
Repeater { Repeater {
id: optionRepeater id: optionRepeater
@@ -120,12 +121,16 @@ Kirigami.Dialog {
} }
QQC2.ToolButton { QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
text: i18nc("@action:button", "Remove option") action: Kirigami.Action {
icon.name: "edit-delete-remove" id: removeOptionAction
onClicked: optionModel.remove(optionDelegate.index) text: i18nc("@action:button", "Remove option")
QQC2.ToolTip.text: text icon.name: "edit-delete-remove"
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay onTriggered: optionModel.remove(optionDelegate.index)
QQC2.ToolTip.visible: hovered }
QQC2.ToolTip {
text: removeOptionAction.text
delay: Kirigami.Units.toolTipDelay
}
} }
} }
} }

View File

@@ -9,6 +9,6 @@ FileDialog {
signal chosen(string path) signal chosen(string path)
title: i18nc("@title:dialog", "Select a File") title: i18n("Select a File")
onAccepted: root.chosen(selectedFile) onAccepted: root.chosen(selectedFile)
} }

View File

@@ -3,6 +3,7 @@
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
@@ -19,7 +20,7 @@ QQC2.Popup {
contentItem: Flow { contentItem: Flow {
QQC2.ToolButton { QQC2.ToolButton {
icon.name: "format-text-bold" icon.name: "format-text-bold"
text: i18nc("@action:button", "Bold") text: i18n("Bold")
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
onClicked: { onClicked: {
@@ -28,7 +29,7 @@ QQC2.Popup {
end: "**", end: "**",
extra: "" extra: ""
}; };
root.formattingSelected(format, root.selectionStart, root.selectionEnd); formattingSelected(format, selectionStart, selectionEnd);
root.close(); root.close();
} }
@@ -38,7 +39,7 @@ QQC2.Popup {
} }
QQC2.ToolButton { QQC2.ToolButton {
icon.name: "format-text-italic" icon.name: "format-text-italic"
text: i18nc("@action:button", "Italic") text: i18n("Italic")
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
onClicked: { onClicked: {
@@ -47,7 +48,7 @@ QQC2.Popup {
end: "*", end: "*",
extra: "" extra: ""
}; };
root.formattingSelected(format, root.selectionStart, root.selectionEnd); formattingSelected(format, selectionStart, selectionEnd);
root.close(); root.close();
} }
@@ -57,7 +58,7 @@ QQC2.Popup {
} }
QQC2.ToolButton { QQC2.ToolButton {
icon.name: "format-text-strikethrough" icon.name: "format-text-strikethrough"
text: i18nc("@action:button", "Strikethrough") text: i18n("Strikethrough")
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
onClicked: { onClicked: {
@@ -66,7 +67,7 @@ QQC2.Popup {
end: "~~", end: "~~",
extra: "" extra: ""
}; };
root.formattingSelected(format, root.selectionStart, root.selectionEnd); formattingSelected(format, selectionStart, selectionEnd);
root.close(); root.close();
} }
@@ -76,7 +77,7 @@ QQC2.Popup {
} }
QQC2.ToolButton { QQC2.ToolButton {
icon.name: "view-hidden-symbolic" icon.name: "view-hidden-symbolic"
text: i18nc("@action:button", "Spoiler") text: i18n("Spoiler")
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
onClicked: { onClicked: {
@@ -85,7 +86,7 @@ QQC2.Popup {
end: "||", end: "||",
extra: "" extra: ""
}; };
root.formattingSelected(format, root.selectionStart, root.selectionEnd); formattingSelected(format, selectionStart, selectionEnd);
root.close(); root.close();
} }
@@ -95,7 +96,7 @@ QQC2.Popup {
} }
QQC2.ToolButton { QQC2.ToolButton {
icon.name: "format-text-code" icon.name: "format-text-code"
text: i18nc("@action:button", "Code block") text: i18n("Code block")
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
onClicked: { onClicked: {
@@ -104,7 +105,7 @@ QQC2.Popup {
end: "`", end: "`",
extra: "" extra: ""
}; };
root.formattingSelected(format, root.selectionStart, root.selectionEnd); formattingSelected(format, selectionStart, selectionEnd);
root.close(); root.close();
} }
@@ -114,16 +115,16 @@ QQC2.Popup {
} }
QQC2.ToolButton { QQC2.ToolButton {
icon.name: "format-text-blockquote" icon.name: "format-text-blockquote"
text: i18nc("@action:button", "Quote") text: i18n("Quote")
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
onClicked: { onClicked: {
const format = { const format = {
start: root.selectionStart == 0 ? ">" : "\n>", start: selectionStart == 0 ? ">" : "\n>",
end: "\n\n", end: "\n\n",
extra: "" extra: ""
}; };
root.formattingSelected(format, root.selectionStart, root.selectionEnd); formattingSelected(format, selectionStart, selectionEnd);
root.close(); root.close();
} }
@@ -133,7 +134,7 @@ QQC2.Popup {
} }
QQC2.ToolButton { QQC2.ToolButton {
icon.name: "link" icon.name: "link"
text: i18nc("@action:button", "Insert link") text: i18n("Insert link")
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
onClicked: { onClicked: {
@@ -142,7 +143,7 @@ QQC2.Popup {
end: "](", end: "](",
extra: ")" extra: ")"
}; };
root.formattingSelected(format, root.selectionStart, root.selectionEnd); formattingSelected(format, selectionStart, selectionEnd);
root.close(); root.close();
} }

View File

@@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org> // SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// 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 org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kitemmodels
import org.kde.neochat import org.kde.neochat
@@ -14,15 +14,14 @@ Kirigami.SearchDialog {
id: root id: root
required property NeoChatConnection connection required property NeoChatConnection connection
required property Kirigami.ApplicationWindow window
Shortcut { Shortcut {
sequence: "Ctrl+K" sequence: "Ctrl+K"
onActivated: if (root.connection) root.open() onActivated: root.open()
} }
onAccepted: if (currentItem) { onAccepted: if (currentItem) {
(currentItem as QQC2.ItemDelegate).clicked(); currentItem.clicked();
} }
onTextChanged: RoomManager.sortFilterRoomListModel.filterText = text onTextChanged: RoomManager.sortFilterRoomListModel.filterText = text
@@ -34,7 +33,7 @@ Kirigami.SearchDialog {
icon.name: "compass" icon.name: "compass"
onTriggered: { onTriggered: {
root.close() root.close()
let dialog = root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), { let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection connection: root.connection
}, { }, {
title: i18nc("@title", "Explore Rooms") title: i18nc("@title", "Explore Rooms")

View File

@@ -7,6 +7,8 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.Page { Kirigami.Page {
id: root id: root

View File

@@ -15,6 +15,8 @@ Kirigami.Dialog {
property var connection property var connection
parent: applicationWindow().overlay
leftPadding: 0 leftPadding: 0
rightPadding: 0 rightPadding: 0
topPadding: 0 topPadding: 0
@@ -22,7 +24,7 @@ Kirigami.Dialog {
title: i18nc("@title Join <name of a space>", "Join %1", SpaceHierarchyCache.recommendedSpaceDisplayName) title: i18nc("@title Join <name of a space>", "Join %1", SpaceHierarchyCache.recommendedSpaceDisplayName)
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24) width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
contentItem: ColumnLayout { contentItem: ColumnLayout {
FormCard.AbstractFormDelegate { FormCard.AbstractFormDelegate {

View File

@@ -4,9 +4,11 @@
import QtQuick import QtQuick
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Window import QtQuick.Window
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kitemmodels
import org.kde.neochat import org.kde.neochat
@@ -62,18 +64,18 @@ Kirigami.Page {
actions: [ actions: [
Kirigami.Action { Kirigami.Action {
visible: Kirigami.Settings.isMobile || !(root.Kirigami.PageStack.pageStack as Kirigami.PageRow).wideMode visible: Kirigami.Settings.isMobile || !applicationWindow().pageStack.wideMode
icon.name: "view-right-new" icon.name: "view-right-new"
onTriggered: (root.QQC2.ApplicationWindow.window as Main).openRoomDrawer() onTriggered: applicationWindow().openRoomDrawer()
} }
] ]
KeyNavigation.left: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).get(0) KeyNavigation.left: pageStack.get(0)
onCurrentRoomChanged: { onCurrentRoomChanged: {
banner.visible = false; banner.visible = false;
if (!Kirigami.Settings.isMobile && chatBarLoader.item) { if (!Kirigami.Settings.isMobile && chatBarLoader.item) {
(chatBarLoader.item as ChatBar).forceActiveFocus(); chatBarLoader.item.forceActiveFocus();
} }
} }
@@ -81,7 +83,7 @@ Kirigami.Page {
target: root.currentRoom.connection target: root.currentRoom.connection
function onIsOnlineChanged() { function onIsOnlineChanged() {
if (!root.currentRoom.connection.isOnline) { if (!root.currentRoom.connection.isOnline) {
banner.text = i18nc("@info:status", "NeoChat is offline. Please check your network connection."); banner.text = i18n("NeoChat is offline. Please check your network connection.");
banner.visible = true; banner.visible = true;
banner.type = Kirigami.MessageType.Error; banner.type = Kirigami.MessageType.Error;
} else { } else {
@@ -136,8 +138,8 @@ Kirigami.Page {
anchors.centerIn: parent anchors.centerIn: parent
sourceComponent: Kirigami.PlaceholderMessage { sourceComponent: Kirigami.PlaceholderMessage {
icon.name: "org.kde.neochat" icon.name: "org.kde.neochat"
text: i18nc("@title", "Welcome to NeoChat") text: i18n("Welcome to NeoChat")
explanation: i18nc("@info:usagetip", "Select or join a room to get started") explanation: i18n("Select or join a room to get started")
} }
} }
@@ -184,10 +186,10 @@ Kirigami.Page {
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.key === Qt.Key_PageUp) { if (event.key === Qt.Key_PageUp) {
event.accepted = true; event.accepted = true;
(timelineViewLoader.item as TimelineView).pageUp(); timelineViewLoader.item.pageUp();
} else if (event.key === Qt.Key_PageDown) { } else if (event.key === Qt.Key_PageDown) {
event.accepted = true; event.accepted = true;
(timelineViewLoader.item as TimelineView).pageDown(); timelineViewLoader.item.pageDown();
} }
} }
@@ -201,10 +203,10 @@ Kirigami.Page {
} }
function onShowEventSource(eventId) { function onShowEventSource(eventId) {
(root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), { applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
sourceText: root.currentRoom.getEventJsonSource(eventId) sourceText: root.currentRoom.getEventJsonSource(eventId)
}, { }, {
title: i18nc("@title:dialog", "Message Source"), title: i18n("Message Source"),
width: Kirigami.Units.gridUnit * 25 width: Kirigami.Units.gridUnit * 25
}); });
} }

View File

@@ -67,14 +67,10 @@ QQC2.ComboBox {
QQC2.ToolButton { QQC2.ToolButton {
visible: serverItem.isAddServerDelegate || serverItem.isDeletable visible: serverItem.isAddServerDelegate || serverItem.isDeletable
icon.name: serverItem.isAddServerDelegate ? "list-add" : "dialog-close" icon.name: serverItem.isAddServerDelegate ? "list-add" : "dialog-close"
text: serverItem.isAddServerDelegate ? i18nc("@action:button", "Add new server") : i18nc("@action:button", "Remove server") text: i18nc("@action:button", "Add new server")
Accessible.name: text Accessible.name: text
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onClicked: { onClicked: {
if (root.currentIndex === serverItem.index && serverItem.isDeletable) { if (root.currentIndex === serverItem.index && serverItem.isDeletable) {
root.currentIndex = 0; root.currentIndex = 0;

View File

@@ -1,9 +1,8 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu> // SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.purpose as Purpose import org.kde.purpose as Purpose
@@ -21,8 +20,10 @@ Kirigami.Action {
id: root id: root
icon.name: "emblem-shared-symbolic" icon.name: "emblem-shared-symbolic"
text: i18nc("@action:button", "Share") text: i18n("Share")
tooltip: i18nc("@info:tooltip", "Share the selected media") tooltip: i18n("Share the selected media")
visible: false
/** /**
* This property holds the input data for purpose. * This property holds the input data for purpose.
@@ -48,21 +49,18 @@ Kirigami.Action {
} }
delegate: Kirigami.Action { delegate: Kirigami.Action {
required property int index property int index
required property string display text: model.display
required property string iconName icon.name: model.iconName
text: display
icon.name: iconName
onTriggered: { onTriggered: {
root.room.download(root.eventId, root.inputData.urls[0]); root.room.download(root.eventId, root.inputData.urls[0]);
root.room.fileTransferCompleted.connect(share); root.room.fileTransferCompleted.connect(share);
} }
function share(id: string): void { function share(id) {
if (id != root.eventId) { if (id != root.eventId) {
return; return;
} }
pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "ShareDialog"), { applicationWindow().pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "ShareDialog"), {
title: root.text, title: root.text,
index: index, index: index,
model: root._instantiator.model model: root._instantiator.model

View File

@@ -6,6 +6,7 @@
*/ */
import QtQuick import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
import org.kde.purpose as Purpose import org.kde.purpose as Purpose
@@ -23,7 +24,7 @@ Kirigami.Page {
bottomPadding: 0 bottomPadding: 0
property alias index: jobView.index property alias index: jobView.index
required property var model property alias model: jobView.model
QQC2.Action { QQC2.Action {
shortcut: 'Escape' shortcut: 'Escape'
@@ -33,7 +34,7 @@ Kirigami.Page {
Notification { Notification {
id: sharingFailed id: sharingFailed
eventId: "Share" eventId: "Share"
text: i18nc("@info:status", "Sharing failed") text: i18n("Sharing failed")
urgency: Notification.NormalUrgency urgency: Notification.NormalUrgency
} }
@@ -50,12 +51,11 @@ Kirigami.Page {
Purpose.JobView { Purpose.JobView {
id: jobView id: jobView
model: root.model
anchors.fill: parent anchors.fill: parent
onStateChanged: { onStateChanged: {
if (state === Purpose.PurposeJobController.Finished) { if (state === Purpose.PurposeJobController.Finished) {
if (jobView.job?.output?.url?.length > 0) { if (jobView.job?.output?.url?.length > 0) {
sharingSuccess.text = i18nc("@info", "Shared url for image is <a href='%1'>%1</a>", jobView.job.output.url); sharingSuccess.text = i18n("Shared url for image is <a href='%1'>%1</a>", jobView.job.output.url);
sharingSuccess.sendEvent(); sharingSuccess.sendEvent();
Clipboard.saveText(jobView.job.output.url); Clipboard.saveText(jobView.job.output.url);
} }

View File

@@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.formcard as FormCard

View File

@@ -142,95 +142,107 @@ Kirigami.Dialog {
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
visible: root.user.id !== root.connection.localUserId && !!root.user visible: root.user.id !== root.connection.localUserId && !!root.user
text: !!root.user && root.connection.isIgnored(root.user.id) ? i18n("Unignore this user") : i18n("Ignore this user") action: Kirigami.Action {
icon.name: "im-invisible-user" text: !!root.user && root.connection.isIgnored(root.user.id) ? i18n("Unignore this user") : i18n("Ignore this user")
onClicked: { icon.name: "im-invisible-user"
root.close(); onTriggered: {
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id); root.close();
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id);
}
} }
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("kick") && room.containsUser(root.user.id) && room.memberEffectivePowerLevel(root.user.id) < room.memberEffectivePowerLevel(root.connection.localUserId) visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("kick") && room.containsUser(root.user.id) && room.memberEffectivePowerLevel(root.user.id) < room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button", "Kick this user") action: Kirigami.Action {
icon.name: "im-kick-user" text: i18n("Kick this user")
onClicked: { icon.name: "im-kick-user"
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { onTriggered: {
title: i18nc("@title:dialog", "Kick User"), let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"), title: i18nc("@title:dialog", "Kick User"),
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"), placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
icon: "im-kick-user" actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
}, { icon: "im-kick-user"
title: i18nc("@title:dialog", "Kick User"), }, {
width: Kirigami.Units.gridUnit * 25 title: i18nc("@title:dialog", "Kick User"),
}); width: Kirigami.Units.gridUnit * 25
dialog.accepted.connect(reason => { });
root.room.kickMember(root.user.id, reason); dialog.accepted.connect(reason => {
}); root.room.kickMember(root.user.id, reason);
root.close(); });
root.close();
}
} }
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("invite") && !room.containsUser(root.user.id) visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("invite") && !room.containsUser(root.user.id)
enabled: root.room && !root.room.isUserBanned(root.user.id) action: Kirigami.Action {
text: i18nc("@action:button", "Invite this user") enabled: root.room && !root.room.isUserBanned(root.user.id)
icon.name: "list-add-user" text: i18n("Invite this user")
onClicked: { icon.name: "list-add-user"
root.room.inviteToRoom(root.user.id); onTriggered: {
root.close(); root.room.inviteToRoom(root.user.id);
root.close();
}
} }
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("ban") && !room.isUserBanned(root.user.id) && room.memberEffectivePowerLevel(root.user.id) < room.memberEffectivePowerLevel(root.connection.localUserId) visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("ban") && !room.isUserBanned(root.user.id) && room.memberEffectivePowerLevel(root.user.id) < room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button", "Ban this user") action: Kirigami.Action {
icon.name: "im-ban-user" text: i18n("Ban this user")
icon.color: Kirigami.Theme.negativeTextColor icon.name: "im-ban-user"
onClicked: { icon.color: Kirigami.Theme.negativeTextColor
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { onTriggered: {
title: i18nc("@title:dialog", "Ban User"), let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
placeholder: i18nc("@info:placeholder", "Reason for banning this user"), title: i18nc("@title:dialog", "Ban User"),
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"), placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
icon: "im-ban-user" actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
}, { icon: "im-ban-user"
title: i18nc("@title:dialog", "Ban User"), }, {
width: Kirigami.Units.gridUnit * 25 title: i18nc("@title:dialog", "Ban User"),
}); width: Kirigami.Units.gridUnit * 25
dialog.accepted.connect(reason => { });
root.room.ban(root.user.id, reason); dialog.accepted.connect(reason => {
}); root.room.ban(root.user.id, reason);
root.close(); });
root.close();
}
} }
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("ban") && room.isUserBanned(root.user.id) visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("ban") && room.isUserBanned(root.user.id)
text: i18nc("@action:button", "Unban this user") action: Kirigami.Action {
icon.name: "im-irc" text: i18n("Unban this user")
icon.color: Kirigami.Theme.negativeTextColor icon.name: "im-irc"
onClicked: { icon.color: Kirigami.Theme.negativeTextColor
root.room.unban(root.user.id); onTriggered: {
root.close(); root.room.unban(root.user.id);
root.close();
}
} }
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
visible: root.room && root.room.canSendState("m.room.power_levels") visible: root.room && root.room.canSendState("m.room.power_levels")
text: i18nc("@action:button", "Set user power level") action: Kirigami.Action {
icon.name: "visibility" text: i18n("Set user power level")
onClicked: { icon.name: "visibility"
let dialog = powerLevelDialog.createObject(this, { onTriggered: {
room: root.room, let dialog = powerLevelDialog.createObject(this, {
userId: root.user.id, room: root.room,
powerLevel: root.room.memberEffectivePowerLevel(root.user.id) userId: root.user.id,
}); powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
dialog.open(); });
root.close(); dialog.open();
root.close();
}
} }
Component { Component {
@@ -244,40 +256,48 @@ Kirigami.Dialog {
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
visible: root.room && (root.user.id === root.connection.localUserId || room.canSendState("redact")) visible: root.room && (root.user.id === root.connection.localUserId || room.canSendState("redact"))
text: i18nc("@action:button", "Remove recent messages by this user") action: Kirigami.Action {
icon.name: "delete" text: i18nc("@action:button", "Remove recent messages by this user")
icon.color: Kirigami.Theme.negativeTextColor icon.name: "delete"
onClicked: { icon.color: Kirigami.Theme.negativeTextColor
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { onTriggered: {
title: i18nc("@title:dialog", "Remove Messages"), let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"), title: i18nc("@title:dialog", "Remove Messages"),
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"), placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
icon: "delete" actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
}, { icon: "delete"
title: i18nc("@title", "Remove Messages"), }, {
width: Kirigami.Units.gridUnit * 25 title: i18nc("@title", "Remove Messages"),
}); width: Kirigami.Units.gridUnit * 25
dialog.accepted.connect(reason => { });
root.room.deleteMessagesByUser(root.user.id, reason); dialog.accepted.connect(reason => {
}); root.room.deleteMessagesByUser(root.user.id, reason);
root.close(); });
root.close();
}
} }
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
visible: root.user.id !== root.connection.localUserId visible: root.user.id !== root.connection.localUserId
text: root.connection.directChatExists(root.user) ? i18nc("%1 is the name of the user.", "Chat with %1", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName)) : i18n("Invite to private chat") action: Kirigami.Action {
icon.name: "document-send" text: root.connection.directChatExists(root.user) ? i18nc("%1 is the name of the user.", "Chat with %1", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName)) : i18n("Invite to private chat")
onClicked: { icon.name: "document-send"
root.connection.requestDirectChat(root.user.id); onTriggered: {
root.close(); root.connection.requestDirectChat(root.user.id);
root.close();
}
} }
} }
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
text: i18n("Copy link") action: Kirigami.Action {
icon.name: "username-copy" text: i18n("Copy link")
onClicked: Clipboard.saveText("https://matrix.to/#/" + root.user.id) icon.name: "username-copy"
onTriggered: {
Clipboard.saveText("https://matrix.to/#/" + root.user.id);
}
}
} }
} }
} }

View File

@@ -236,19 +236,11 @@ void RoomManager::resolveResource(Uri uri, const QString &action)
} }
} }
void RoomManager::maximizeMedia(const QString &eventId) void RoomManager::maximizeMedia(int index)
{ {
if (eventId.isEmpty()) { if (index < -1 || index > m_mediaMessageFilterModel->rowCount()) {
qWarning() << "Tried to open media for empty event id";
return; return;
} }
const auto index = m_mediaMessageFilterModel->getRowForEventId(eventId);
if (index == -1) {
qWarning() << "Tried to open media for unknown event id" << eventId;
return;
}
Q_EMIT showMaximizedMedia(index); Q_EMIT showMaximizedMedia(index);
} }
@@ -272,17 +264,8 @@ void RoomManager::viewEventSource(const QString &eventId)
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText, const QString &hoveredLink) void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText, const QString &hoveredLink)
{ {
if (eventId.isEmpty()) { const auto &event = **room->findInTimeline(eventId);
qWarning() << "Tried to open event menu with empty event id";
return;
}
const auto it = room->findInTimeline(eventId);
if (it == room->historyEdge()) {
// This is probably a pending event
return;
}
const auto &event = **it;
if (EventHandler::mediaInfo(room, &event).contains("mimeType"_L1)) { if (EventHandler::mediaInfo(room, &event).contains("mimeType"_L1)) {
Q_EMIT showFileMenu(eventId, Q_EMIT showFileMenu(eventId,
sender, sender,
@@ -375,6 +358,19 @@ void RoomManager::visitRoom(Room *r, const QString &eventId)
if (m_currentRoom && !m_currentRoom->editCache()->editId().isEmpty()) { if (m_currentRoom && !m_currentRoom->editCache()->editId().isEmpty()) {
m_currentRoom->editCache()->setEditId({}); m_currentRoom->editCache()->setEditId({});
} }
if (m_currentRoom && !m_currentRoom->isSpace() && m_chatDocumentHandler) {
// We're doing these things here because it is critical that they are switched at the same time
if (m_chatDocumentHandler->document()) {
m_currentRoom->mainCache()->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
m_chatDocumentHandler->setRoom(room);
if (room) {
m_chatDocumentHandler->document()->textDocument()->setPlainText(room->mainCache()->savedText());
room->mainCache()->setText(room->mainCache()->savedText());
}
} else {
m_chatDocumentHandler->setRoom(room);
}
}
if (!room) { if (!room) {
setCurrentRoom({}); setCurrentRoom({});
@@ -474,6 +470,18 @@ bool RoomManager::visitNonMatrix(const QUrl &url)
return true; return true;
} }
ChatDocumentHandler *RoomManager::chatDocumentHandler() const
{
return m_chatDocumentHandler;
}
void RoomManager::setChatDocumentHandler(ChatDocumentHandler *handler)
{
m_chatDocumentHandler = handler;
m_chatDocumentHandler->setRoom(m_currentRoom);
Q_EMIT chatDocumentHandlerChanged();
}
void RoomManager::setConnection(NeoChatConnection *connection) void RoomManager::setConnection(NeoChatConnection *connection)
{ {
if (m_connection == connection) { if (m_connection == connection) {

View File

@@ -11,6 +11,7 @@
#include <Quotient/roommember.h> #include <Quotient/roommember.h>
#include <Quotient/uriresolver.h> #include <Quotient/uriresolver.h>
#include "chatdocumenthandler.h"
#include "enums/messagecomponenttype.h" #include "enums/messagecomponenttype.h"
#include "enums/messagetype.h" #include "enums/messagetype.h"
#include "models/mediamessagefiltermodel.h" #include "models/mediamessagefiltermodel.h"
@@ -136,6 +137,13 @@ class RoomManager : public QObject, public UriResolverBase
*/ */
Q_PROPERTY(bool hasOpenRoom READ hasOpenRoom NOTIFY currentRoomChanged) Q_PROPERTY(bool hasOpenRoom READ hasOpenRoom NOTIFY currentRoomChanged)
/**
* @brief The ChatDocumentHandler for the open room.
*
* @sa ChatDocumentHandler
*/
Q_PROPERTY(ChatDocumentHandler *chatDocumentHandler READ chatDocumentHandler WRITE setChatDocumentHandler NOTIFY chatDocumentHandlerChanged)
public: public:
virtual ~RoomManager(); virtual ~RoomManager();
static RoomManager &instance(); static RoomManager &instance();
@@ -204,8 +212,12 @@ public:
/** /**
* @brief Show a media item maximized. * @brief Show a media item maximized.
*
* @param index the index to open the maximize delegate model at. This is the
* index in the MediaMessageFilterModel owned by this RoomManager. A value
* of -1 opens a the default item.
*/ */
Q_INVOKABLE void maximizeMedia(const QString &eventId); Q_INVOKABLE void maximizeMedia(int index);
Q_INVOKABLE void maximizeCode(NeochatRoomMember *author, const QDateTime &time, const QString &codeText, const QString &language); Q_INVOKABLE void maximizeCode(NeochatRoomMember *author, const QDateTime &time, const QString &codeText, const QString &language);
@@ -225,6 +237,9 @@ public:
Q_INVOKABLE void Q_INVOKABLE void
viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText = {}, const QString &hoveredLink = {}); viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText = {}, const QString &hoveredLink = {});
ChatDocumentHandler *chatDocumentHandler() const;
void setChatDocumentHandler(ChatDocumentHandler *handler);
/** /**
* @brief Set a URL to be loaded as the initial room. * @brief Set a URL to be loaded as the initial room.
*/ */
@@ -327,6 +342,8 @@ Q_SIGNALS:
*/ */
void showMessage(MessageType::Type messageType, const QString &message); void showMessage(MessageType::Type messageType, const QString &message);
void chatDocumentHandlerChanged();
void connectionChanged(); void connectionChanged();
void directChatsActiveChanged(); void directChatsActiveChanged();
@@ -355,6 +372,7 @@ private:
KConfigGroup m_lastRoomConfig; KConfigGroup m_lastRoomConfig;
KConfigGroup m_lastSpaceConfig; KConfigGroup m_lastSpaceConfig;
KConfigGroup m_directChatsConfig; KConfigGroup m_directChatsConfig;
QPointer<ChatDocumentHandler> m_chatDocumentHandler;
RoomListModel *m_roomListModel; RoomListModel *m_roomListModel;
SortFilterRoomListModel *m_sortFilterRoomListModel; SortFilterRoomListModel *m_sortFilterRoomListModel;

View File

@@ -5,8 +5,6 @@
#include <QDBusMetaType> #include <QDBusMetaType>
#include <KWindowSystem>
#include "controller.h" #include "controller.h"
#include "models/roomlistmodel.h" #include "models/roomlistmodel.h"
#include "models/sortfilterroomlistmodel.h" #include "models/sortfilterroomlistmodel.h"
@@ -88,9 +86,4 @@ void Runner::Run(const QString &id, const QString &actionId)
WindowController::instance().showAndRaiseWindow(QString()); WindowController::instance().showAndRaiseWindow(QString());
} }
void Runner::SetActivationToken(const QString &token)
{
KWindowSystem::setCurrentXdgActivationToken(token);
}
#include "moc_runner.cpp" #include "moc_runner.cpp"

View File

@@ -190,8 +190,6 @@ public:
*/ */
Q_SCRIPTABLE void Run(const QString &id, const QString &actionId); Q_SCRIPTABLE void Run(const QString &id, const QString &actionId);
Q_SCRIPTABLE void SetActivationToken(const QString &token);
Q_SIGNALS: Q_SIGNALS:
void roomListModelChanged(); void roomListModelChanged();

View File

@@ -26,11 +26,11 @@ QQC2.Popup {
icon.name: 'mail-attachment' icon.name: 'mail-attachment'
text: i18nc("@action:button", "Choose local file") text: i18n("Choose local file")
onClicked: { onClicked: {
root.close(); root.close();
var fileDialog = openFileDialog.createObject(QQC2.Overlay.overlay) as OpenFileDialog; var fileDialog = openFileDialog.createObject(QQC2.Overlay.overlay);
fileDialog.chosen.connect(path => root.chosen(path)); fileDialog.chosen.connect(path => root.chosen(path));
fileDialog.open(); fileDialog.open();
} }
@@ -42,7 +42,7 @@ QQC2.Popup {
Layout.fillHeight: true Layout.fillHeight: true
icon.name: 'insert-image' icon.name: 'insert-image'
text: i18nc("@action:button", "Clipboard image") text: i18n("Clipboard image")
onClicked: { onClicked: {
const path = StandardPaths.standardLocations(StandardPaths.CacheLocation)[0] + "/screenshots/" + (new Date()).getTime() + ".png"; const path = StandardPaths.standardLocations(StandardPaths.CacheLocation)[0] + "/screenshots/" + (new Date()).getTime() + ".png";
if (!Clipboard.saveImage(path)) { if (!Clipboard.saveImage(path)) {

View File

@@ -56,7 +56,7 @@ QQC2.Control {
} }
Connections { Connections {
target: root.currentRoom.mainCache target: currentRoom.mainCache
function onMentionAdded(mention: string): void { function onMentionAdded(mention: string): void {
// add mention text // add mention text
@@ -74,34 +74,34 @@ QQC2.Control {
* Each of these will be visualised in the ChatBar so new actions can be added * Each of these will be visualised in the ChatBar so new actions can be added
* by appending to this list. * by appending to this list.
*/ */
property list<BusyAction> actions: [ property list<Kirigami.Action> actions: [
BusyAction { Kirigami.Action {
id: attachmentAction id: attachmentAction
isBusy: root.currentRoom && root.currentRoom.hasFileUploading property bool isBusy: root.currentRoom && root.currentRoom.hasFileUploading
// Matrix does not allow sending attachments in replies // Matrix does not allow sending attachments in replies
visible: _private.chatBarCache.replyId.length === 0 && _private.chatBarCache.attachmentPath.length === 0 visible: _private.chatBarCache.replyId.length === 0 && _private.chatBarCache.attachmentPath.length === 0
icon.name: "mail-attachment" icon.name: "mail-attachment"
text: i18nc("@action:button", "Attach an image or file") text: i18n("Attach an image or file")
displayHint: Kirigami.DisplayHint.IconOnly displayHint: Kirigami.DisplayHint.IconOnly
onTriggered: { onTriggered: {
let dialog = (Clipboard.hasImage ? attachDialog : openFileDialog).createObject(root.QQC2.Overlay.overlay); let dialog = (Clipboard.hasImage ? attachDialog : openFileDialog).createObject(applicationWindow().overlay);
dialog.chosen.connect(path => _private.chatBarCache.attachmentPath = path); dialog.chosen.connect(path => _private.chatBarCache.attachmentPath = path);
dialog.open(); dialog.open();
} }
tooltip: text tooltip: text
}, },
BusyAction { Kirigami.Action {
id: emojiAction id: emojiAction
isBusy: false property bool isBusy: false
visible: !Kirigami.Settings.isMobile visible: !Kirigami.Settings.isMobile
icon.name: "smiley" icon.name: "smiley"
text: i18nc("@action:button", "Emojis & Stickers") text: i18n("Emojis & Stickers")
displayHint: Kirigami.DisplayHint.IconOnly displayHint: Kirigami.DisplayHint.IconOnly
checkable: true checkable: true
@@ -114,11 +114,11 @@ QQC2.Control {
} }
tooltip: text tooltip: text
}, },
BusyAction { Kirigami.Action {
id: mapButton id: mapButton
icon.name: "mark-location-symbolic" icon.name: "mark-location-symbolic"
isBusy: false property bool isBusy: false
text: i18nc("@action:button", "Send a Location") text: i18n("Send a Location")
displayHint: QQC2.AbstractButton.IconOnly displayHint: QQC2.AbstractButton.IconOnly
onTriggered: { onTriggered: {
@@ -128,10 +128,10 @@ QQC2.Control {
} }
tooltip: text tooltip: text
}, },
BusyAction { Kirigami.Action {
id: pollButton id: pollButton
icon.name: "amarok_playcount" icon.name: "amarok_playcount"
isBusy: false property bool isBusy: false
text: i18nc("@action:button", "Create a Poll") text: i18nc("@action:button", "Create a Poll")
displayHint: QQC2.AbstractButton.IconOnly displayHint: QQC2.AbstractButton.IconOnly
@@ -142,13 +142,13 @@ QQC2.Control {
} }
tooltip: text tooltip: text
}, },
BusyAction { Kirigami.Action {
id: sendAction id: sendAction
isBusy: false property bool isBusy: false
icon.name: "document-send" icon.name: "document-send"
text: i18nc("@action:button", "Send message") text: i18n("Send message")
displayHint: Kirigami.DisplayHint.IconOnly displayHint: Kirigami.DisplayHint.IconOnly
checkable: true checkable: true
@@ -191,34 +191,18 @@ QQC2.Control {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing Layout.margins: Kirigami.Units.largeSpacing
Layout.preferredHeight: active ? (item as Item).implicitHeight : 0 Layout.preferredHeight: active ? item.implicitHeight : 0
active: visible active: visible
visible: root.currentRoom.mainCache.replyId.length > 0 visible: root.currentRoom.mainCache.replyId.length > 0
sourceComponent: replyPane sourceComponent: replyPane
} }
RowLayout {
visible: replyLoader.visible && !root.currentRoom.mainCache.relationAuthorIsPresent
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
source: "help-hint-symbolic"
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
}
QQC2.Label {
text: i18nc("@info", "The user you're replying to has left the room, and can't be notified.")
color: Kirigami.Theme.disabledTextColor
}
}
Loader { Loader {
id: attachLoader id: attachLoader
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing Layout.margins: Kirigami.Units.largeSpacing
Layout.preferredHeight: active ? (item as Item).implicitHeight : 0 Layout.preferredHeight: active ? item.implicitHeight : 0
active: visible active: visible
visible: root.currentRoom.mainCache.attachmentPath.length > 0 visible: root.currentRoom.mainCache.attachmentPath.length > 0
@@ -250,10 +234,9 @@ QQC2.Control {
QQC2.TextArea { QQC2.TextArea {
id: textField id: textField
placeholderText: root.currentRoom.usesEncryption ? i18nc("@placeholder", "Send an encrypted message…") : root.currentRoom.mainCache.attachmentPath.length > 0 ? i18nc("@placeholder", "Set an attachment caption…") : i18nc("@placeholder", "Send a message…") placeholderText: root.currentRoom.usesEncryption ? i18n("Send an encrypted message…") : root.currentRoom.mainCache.attachmentPath.length > 0 ? i18n("Set an attachment caption…") : i18n("Send a message…")
verticalAlignment: TextEdit.AlignVCenter verticalAlignment: TextEdit.AlignVCenter
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
textFormat: TextEdit.MarkdownText
Accessible.description: placeholderText Accessible.description: placeholderText
@@ -270,6 +253,7 @@ QQC2.Control {
root.currentRoom.sendTypingNotification(textExists); root.currentRoom.sendTypingNotification(textExists);
textExists ? repeatTimer.start() : repeatTimer.stop(); textExists ? repeatTimer.start() : repeatTimer.stop();
} }
_private.chatBarCache.text = text;
} }
onSelectedTextChanged: { onSelectedTextChanged: {
if (selectedText.length > 0) { if (selectedText.length > 0) {
@@ -285,7 +269,7 @@ QQC2.Control {
x: textField.cursorRectangle.x x: textField.cursorRectangle.x
y: textField.cursorRectangle.y - height y: textField.cursorRectangle.y - height
onFormattingSelected: (format, selectionStart, selectionEnd) => _private.formatText(format, selectionStart, selectionEnd) onFormattingSelected: _private.formatText(format, selectionStart, selectionEnd)
} }
Keys.onEnterPressed: event => { Keys.onEnterPressed: event => {
@@ -363,8 +347,6 @@ QQC2.Control {
Repeater { Repeater {
model: root.actions model: root.actions
delegate: QQC2.ToolButton { delegate: QQC2.ToolButton {
id: actionDelegate
required property BusyAction modelData
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source) icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
onClicked: modelData.trigger() onClicked: modelData.trigger()
@@ -375,7 +357,7 @@ QQC2.Control {
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
contentItem: PieProgressBar { contentItem: PieProgressBar {
visible: actionDelegate.modelData.isBusy visible: modelData.isBusy
progress: root.currentRoom.fileUploadingProgress progress: root.currentRoom.fileUploadingProgress
} }
} }
@@ -400,6 +382,8 @@ QQC2.Control {
implicitHeight: replyComponent.implicitHeight implicitHeight: replyComponent.implicitHeight
ReplyComponent { ReplyComponent {
id: replyComponent id: replyComponent
replyEventId: _private.chatBarCache.replyId
replyAuthor: _private.chatBarCache.relationAuthor
replyContentModel: ContentProvider.contentModelForEvent(root.currentRoom, _private.chatBarCache.replyId, true) replyContentModel: ContentProvider.contentModelForEvent(root.currentRoom, _private.chatBarCache.replyId, true)
Message.maxContentWidth: replyLoader.item.width Message.maxContentWidth: replyLoader.item.width
@@ -440,6 +424,7 @@ QQC2.Control {
QtObject { QtObject {
id: _private id: _private
property ChatBarCache chatBarCache property ChatBarCache chatBarCache
onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache
function postMessage() { function postMessage() {
_private.chatBarCache.postMessage(); _private.chatBarCache.postMessage();
@@ -501,9 +486,15 @@ QQC2.Control {
ChatDocumentHandler { ChatDocumentHandler {
id: documentHandler id: documentHandler
type: ChatBarType.Room document: textField.textDocument
textItem: textField cursorPosition: textField.cursorPosition
room: root.currentRoom selectionStart: textField.selectionStart
selectionEnd: textField.selectionEnd
mentionColor: Kirigami.Theme.linkColor
errorColor: Kirigami.Theme.negativeTextColor
Component.onCompleted: {
RoomManager.chatDocumentHandler = documentHandler;
}
} }
Component { Component {
@@ -562,7 +553,7 @@ QQC2.Control {
currentRoom: root.currentRoom currentRoom: root.currentRoom
onChosen: emoji => root.insertText(emoji) onChosen: emoji => insertText(emoji)
onClosed: if (emojiAction.checked) { onClosed: if (emojiAction.checked) {
emojiAction.checked = false; emojiAction.checked = false;
} }
@@ -573,8 +564,4 @@ QQC2.Control {
textField.text = textField.text.substr(0, initialCursorPosition) + text + textField.text.substr(initialCursorPosition); textField.text = textField.text.substr(0, initialCursorPosition) + text + textField.text.substr(initialCursorPosition);
textField.cursorPosition = initialCursorPosition + text.length; textField.cursorPosition = initialCursorPosition + text.length;
} }
component BusyAction : Kirigami.Action {
required property bool isBusy
}
} }

View File

@@ -65,7 +65,7 @@ QQC2.Popup {
padding: 2 padding: 2
implicitHeight: Kirigami.Units.gridUnit * 20 + 2 * padding implicitHeight: Kirigami.Units.gridUnit * 20 + 2 * padding
width: Math.min(contentItem.categoryIconSize * 11 + 2 * padding, QQC2.ApplicationWindow.window.width) width: Math.min(contentItem.categoryIconSize * 11 + 2 * padding, applicationWindow().width)
contentItem: EmojiPicker { contentItem: EmojiPicker {
id: emojiPicker id: emojiPicker
height: 400 height: 400

View File

@@ -20,13 +20,6 @@ FormCard.FormCard {
onToggled: NeoChatConfig.showAllEvents = checked onToggled: NeoChatConfig.showAllEvents = checked
} }
FormCard.FormCheckDelegate {
text: i18nc("@option:check", "Allow sending relations to any event in the timeline")
description: i18nc("@info", "This includes state events")
checked: NeoChatConfig.relateAnyEvent
onToggled: NeoChatConfig.relateAnyEvent = checked
}
FormCard.FormCheckDelegate { FormCard.FormCheckDelegate {
id: roomAccountDataVisibleCheck id: roomAccountDataVisibleCheck
text: i18nc("@option:check Enable the matrix 'threads' feature", "Always allow device verification") text: i18nc("@option:check Enable the matrix 'threads' feature", "Always allow device verification")

Some files were not shown because too many files have changed in this diff Show More