Compare commits

..

226 Commits

Author SHA1 Message Date
l10n daemon script
2ea6e0425a GIT_SILENT Sync po/docbooks with svn 2025-11-03 03:13:11 +00:00
Heiko Becker
fe55ff19d2 Fix version in appstream file
GIT_SILENT

(cherry picked from commit 40694f502a)
2025-11-01 20:02:22 +01:00
Heiko Becker
c35bdc0592 GIT_SILENT Upgrade release service version to 25.08.3. 2025-10-31 01:57:04 +01:00
Heiko Becker
c6fa5a10dd GIT_SILENT Update Appstream for new release 2025-10-31 01:23:14 +01:00
l10n daemon script
f956c33b82 GIT_SILENT Sync po/docbooks with svn 2025-10-29 03:19:25 +00:00
Joshua Goins
0b9295e67e Show less scary icon for neutral reasons when cancelling verification
For example, accepting a verification on another device shows a giant
red security icon which isn't really suitable. I chucked the
dialog-information icon in for some of the neutral-sounding messages so
this dialog can be a little less intimidating.

BUG: 510421
FIXED-IN: 24.08.3


(cherry picked from commit be92e56c3a)

Co-authored-by: Joshua Goins <josh@redstrate.com>
2025-10-28 09:16:51 -04:00
Joshua Goins
0fdade83e0 Fix jump to previous/next unread room shortcuts
These were dependent on a renamed property and ended up breaking, oops.

(cherry picked from commit c778ba8b24)
2025-10-27 18:09:42 -04:00
l10n daemon script
6a25945131 GIT_SILENT Sync po/docbooks with svn 2025-10-25 03:20:51 +00:00
l10n daemon script
218222bc58 GIT_SILENT made messages (after extraction) 2025-10-22 02:35:06 +00:00
l10n daemon script
4300bba804 GIT_SILENT Sync po/docbooks with svn 2025-10-21 03:15:16 +00:00
l10n daemon script
13f766d166 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-21 03:11:53 +00:00
l10n daemon script
fd4e701c51 GIT_SILENT Sync po/docbooks with svn 2025-10-18 04:10:39 +00:00
l10n daemon script
d8ff639374 GIT_SILENT Sync po/docbooks with svn 2025-10-17 04:09:02 +00:00
l10n daemon script
4a50281152 GIT_SILENT Sync po/docbooks with svn 2025-10-16 03:17:41 +00:00
l10n daemon script
acc9289d06 GIT_SILENT Sync po/docbooks with svn 2025-10-14 03:18:05 +00:00
l10n daemon script
235bd21eaf GIT_SILENT Sync po/docbooks with svn 2025-10-09 03:23:35 +00:00
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
l10n daemon script
87213903b1 GIT_SILENT Sync po/docbooks with svn 2025-07-05 01:39:27 +00:00
James Graham
3d9d211d25 Allow the condition for when messages are automatically marked as read to be configurable.
Title this adds a number of options for when messages should be automatically marked as read for the user to choose from.

![image](/uploads/cef95f8c6c77bfdcabb7a8a309bc1fd2/image.png){width=480 height=262}
2025-07-04 14:36:36 +01:00
James Graham
d5d291396d MessageContentModel improvements
Remove the hack for DelegateChooser and minimise the amount of updates when data changes
2025-07-04 10:28:50 +01:00
l10n daemon script
d7cd38b5e5 GIT_SILENT Sync po/docbooks with svn 2025-07-04 01:39:05 +00:00
l10n daemon script
1a25cfc0e3 GIT_SILENT Sync po/docbooks with svn 2025-07-03 07:51:46 +00:00
l10n daemon script
8d9669e033 GIT_SILENT Sync po/docbooks with svn 2025-07-02 11:07:52 +00:00
l10n daemon script
690d2e2b12 GIT_SILENT Sync po/docbooks with svn 2025-07-02 08:01:01 +00:00
l10n daemon script
2208b1b2b3 GIT_SILENT Sync po/docbooks with svn 2025-07-02 01:40:33 +00:00
Joshua Goins
67da272ccd Add feature flag for the new calls feature
This will be experimental for some time, so we need a way to turn it
on/off for future stable builds.
2025-07-01 14:52:46 -04:00
Joshua Goins
171930fb1d Show a message when failing to download a file
And optionally show an error message, if libQuotient gets one. This
helps in certain rare situations where you just *Cannot* download
something. Before, NeoChat would just reset the image or video without
telling you anything but now there's a nice big error message telling
you what's wrong.
2025-07-01 06:53:59 -04:00
l10n daemon script
2bd59fa16f GIT_SILENT Sync po/docbooks with svn 2025-07-01 01:40:14 +00:00
Joshua Goins
d0d0384bdb Improve the look of loading videos
The video thumbnail is no longer hidden while loading. This makes it
look less buggy (like NeoChat forgot what the thumbnail was) while
loading a large video. To help with contrast, a slight background tint
is added.
2025-06-30 17:21:40 -04:00
James Graham
680573de61 Make sure that thread replies aren't hidden when threads are off 2025-06-30 19:34:27 +01:00
Heiko Becker
ba7f866cf5 GIT_SILENT Update Appstream for new release
(cherry picked from commit 7e87d415ae)
2025-06-30 18:57:23 +02:00
Tobias Fella
57a95ae972 Show nicer text for call member events 2025-06-30 17:17:47 +02:00
Marco Martin
de97275a38 Use new Kirigami builtin column resize handle
Use the new Kirigami.ColumnView.interactiveResizeEnabled attached property
which allows to have a built in resize handle for ColumnView pages
removing the need for a custom handle with resize logic

depends from https://invent.kde.org/frameworks/kirigami/-/merge_requests/1795
2025-06-30 11:25:28 +02:00
l10n daemon script
d08181f56d GIT_SILENT Sync po/docbooks with svn 2025-06-30 01:41:18 +00:00
James Graham
f6e8491bf1 Split message content into its own module
This is laying some groundwork for the rich text chatbar.
2025-06-29 12:43:48 +01:00
l10n daemon script
a1447ebd6f GIT_SILENT Sync po/docbooks with svn 2025-06-29 01:44:03 +00:00
l10n daemon script
3b67e4deaf GIT_SILENT Sync po/docbooks with svn 2025-06-28 01:39:59 +00:00
James Graham
e55afd0402 Move ImageEditorPage to Chatbar module 2025-06-27 16:25:57 +01:00
James Graham
7c0710d445 ActionToolbar is no longer needed now there is only 1 button 2025-06-27 16:08:30 +01:00
James Graham
61f9cd41f7 Initial Account
Restore the functionality where clicking on "edit this account" from the AccountMenu opens setting to the account rather than pushing as its own window
2025-06-27 16:08:14 +01:00
l10n daemon script
6c7a7a7be5 GIT_SILENT Sync po/docbooks with svn 2025-06-27 01:41:18 +00:00
James Graham
63125d97c3 Fix hiding down button on timeline when at the bottom 2025-06-26 14:38:16 +01:00
James Graham
cb4e7d4943 Fix room list header when collapsed 2025-06-26 14:34:18 +01:00
l10n daemon script
b9d5ed699e GIT_SILENT Sync po/docbooks with svn 2025-06-26 01:41:04 +00:00
l10n daemon script
75fe5c8970 GIT_SILENT Sync po/docbooks with svn 2025-06-25 01:42:54 +00:00
Aleix Pol
91da2d01b7 UserInfo: Move the switch user action to the menu
It doesn't clutter the main UI.
It allows us to see the keyboard shortcut.
2025-06-23 23:19:05 +00:00
l10n daemon script
4bade72ce4 GIT_SILENT Sync po/docbooks with svn 2025-06-23 01:39:47 +00:00
l10n daemon script
9c05e2feed GIT_SILENT Sync po/docbooks with svn 2025-06-22 01:45:29 +00:00
l10n daemon script
ba94098411 GIT_SILENT Sync po/docbooks with svn 2025-06-21 01:41:18 +00:00
James Graham
43c691160e Update the RoomTreeSection to use standard Kirigami.ListSectionHeader 2025-06-20 07:42:43 +01:00
James Graham
e2daa091e8 Switch section delegate for standard Kirigami.ListSectionHeader
Switch section delegate for standard Kirigami.ListSectionHeader, SectionDelegate is no longer required so removed.
2025-06-20 07:42:14 +01:00
l10n daemon script
f026414b1a GIT_SILENT Sync po/docbooks with svn 2025-06-20 01:39:04 +00:00
l10n daemon script
fd640a4bd0 GIT_SILENT Sync po/docbooks with svn 2025-06-19 01:39:06 +00:00
l10n daemon script
dd433d7f99 GIT_SILENT Sync po/docbooks with svn 2025-06-18 01:39:30 +00:00
James Graham
3183e00acc Fix Use After Free MessageDelegateBase
Make sure that a `MessageDelegateBase` is not used after free by a `MessageObjectIncubator` callback by tracking them and cleaning them up on deletion of a `MessageDelegateBase`
2025-06-17 17:19:38 +01:00
James Graham
1860de12ea RoomPage cleanup
This does some further cleanup of RoomPage, mostly removing all the vestigial bits from when we could have multiple windows. But also stuff is moved to TimelineView where possible.

The loading placeholder is removed as TimelineModel already has this built in.

TimelineView now gets room from it's model. This is to ensure we're always using the same room as it which may not be true momentarily when RoomManager.currentRoom changes as the model does it's own reset sequence. This will prevent some race conditions in future (and which I already hit creating other MRs)
2025-06-17 16:41:38 +01:00
l10n daemon script
277f1ab252 GIT_SILENT Sync po/docbooks with svn 2025-06-17 01:40:15 +00:00
Thiago Sueto
324b332fa6 Add confirmation prompt to reset all configuration
Resetting all configuration is destructive, it should require a confirmation prompt instead of triggering immediately.
2025-06-16 18:00:06 +00:00
James Graham
33d29f6b02 Fix jump to last message button trigger, the list view is no longer root. 2025-06-16 18:04:25 +01:00
l10n daemon script
419aed6375 GIT_SILENT Sync po/docbooks with svn 2025-06-16 01:39:23 +00:00
Aleix Pol
7bb26ac3be main: Add a replace option
Much like many other unique KDE components, add --replace so the running
instance can be replaced by the new one. Useful for development.
2025-06-15 23:07:40 +02:00
Aleix Pol
7654333ec1 ImageComponent: Can't make a qreal undefined
Using -1 because MediaSizeHelper is already handling negative numbers as
such.
2025-06-15 23:07:40 +02:00
Aleix Pol
d3a2da391d flatpak: Use the Qt 6.9 runtime
It's all the fresh!!
2025-06-15 23:07:40 +02:00
Aleix Pol
f19f59c37f EmojiPicker: Fix delegate for stickers
There's no avatarUrl in ImagePacksModel, it's just url.
2025-06-15 23:07:40 +02:00
Aleix Pol
94e53e14a3 UserInfo: Use "Account" naming consistently
We use "Account" in the settings so we better use the same concept
across the UI.
2025-06-15 23:06:15 +02:00
Manuel Alcaraz Zambrano
d2b3788872 Remove QCoro from Flatpak
QCoro is already available on the runtime.
2025-06-15 12:07:24 +02:00
l10n daemon script
8095062db2 GIT_SILENT Sync po/docbooks with svn 2025-06-15 01:40:14 +00:00
Tobias Fella
89beaa9316 Remove stray warning 2025-06-15 00:29:34 +02:00
Carl Schwan
dfaffd043e Cleanup craft config
Don't build master version of some modules
2025-06-14 12:13:31 +00:00
Tobias Fella
a8c200222f Don't parent PollHandler to room
PollHandlers are stored in a QCache, which takes over ownership of the object. At the same time, PollHandler currently relies on its parent being the room.
Somehow, this didn't explode entirely, but only leads to minor problems like crashes on shutdown.
2025-06-14 12:13:16 +00:00
Tobias Fella
598a1c28ac Add button for hiding images and videos 2025-06-14 12:12:01 +00:00
Tobias Fella
b972703f34 Refactor and fix opening file-based content
The relevant fix is checking whether the the event is file-*based* (i.e., image, video, audio, file), not whether it *is* a file
2025-06-14 12:07:56 +00:00
Tobias Fella
d30fdc67c6 Remove file logging
This hasn't proven to be as useful as i had hoped:
- My arcane logic for determining logging categories is apparently broken
- It won't work with the logging by the new crypto sdk
- I never actually ended up looking at my own logs, or anyone else's
- It seems to cause crashes
2025-06-14 11:57:23 +00:00
James Graham
6a5a2e6144 Refactor TimelineView
Refactor TimelineView to make it more reliable and prepare for read marker choice. This is done by creating signalling from the mode when reset which can be used to move the scrollbar to the newest meassage.

Some of the spaghetti is also removed so there is no need for ChatBar and TimelineView to talk directly.

The code to mark messages as read if they are all visible after 10s has been removed infour of just marking as read on entry if all are visible. This is temporary until a follow up providing user options is finished (although it will be one of the options)
2025-06-14 12:16:39 +01:00
Kai Uwe Broulik
235143528c MaximizeComponent: Fix download location
No idea where "saveFolder" was supposed to be coming from but this
makes it use the last location or default to downloads, consistent
with the other save dialogs.
2025-06-14 10:08:12 +02:00
l10n daemon script
33ca2b8d09 GIT_SILENT Sync po/docbooks with svn 2025-06-14 01:40:03 +00:00
Aleix Pol
50176cfafb ImageComponent: Do not set undefined to int
An int property cannot be undefined, make it var.
2025-06-13 15:46:30 +00:00
Aleix Pol
a67fb36a84 SpaceHierarchyDelegate: Fix opening rooms
RoomManager is in org.kde.neochat and needs importing
2025-06-13 15:46:30 +00:00
Aleix Pol
aea9984187 DevicesModel: Do not connect until the value is initialised
When the DevicesModel is open, we get a nullptr instance. I guess it's
obvious that it's null because it hasn't been set yet. Move the connect
to the setter.
2025-06-13 15:46:30 +00:00
Freya Lupen
7a6c234b40 macOS: Set app bundle properties
This fixes the app's name being blank in the system.
2025-06-13 08:25:45 +00:00
Freya Lupen
4cdaa513d3 Use Breeze style on macOS, as on other platforms 2025-06-13 08:25:45 +00:00
l10n daemon script
aaf655ea5b GIT_SILENT Sync po/docbooks with svn 2025-06-13 01:39:47 +00:00
Tobias Fella
207c824ec7 Use QML safe member for invitations
Has the sideeffect of making sure that the avatar colors are the same between the invitation view and the sidebar
2025-06-12 12:49:01 +00:00
Tobias Fella
29abe0bacb Use ImageComponent for replies
This automatically gives us the image hiding safety feature

BUG: 503442
2025-06-12 09:04:52 +00:00
Tobias Fella
d14eda2ca0 Hide avatar of inviting user when images are hidden by default
While this reuses the setting for a slightly different purpose, in practice, these safety feature really belong together and it makes sense to have them both under a single option.
In the future, we might want to rephrase the options description
2025-06-11 22:42:49 +02:00
l10n daemon script
0425cf41d0 GIT_SILENT Sync po/docbooks with svn 2025-06-11 01:45:08 +00:00
l10n daemon script
4ff014f288 GIT_SILENT Sync po/docbooks with svn 2025-06-09 01:42:48 +00:00
l10n daemon script
2e24500866 GIT_SILENT Sync po/docbooks with svn 2025-06-08 01:41:02 +00:00
Joshua Goins
f13a256150 Refactor receiveRichPlainUrl text handler test to be data-based
I was originally going to add another test case to this, turns out I
didn't need to. Might as well ship this anyway, as it will make it
trivial to add more in the future.
2025-06-07 07:30:28 -04:00
l10n daemon script
c958e8cba9 GIT_SILENT Sync po/docbooks with svn 2025-06-06 01:38:09 +00:00
Joshua Goins
7668da68d3 Allow accepting reason dialogs with Ctrl+Enter
The original suggestion was the Enter key, but this won't work well as
reasons can take multiple lines. I also made sure the reason control was
focused by default, and that the "Cancel" button has an icon.

BUG: 500990
2025-06-05 12:03:37 +00:00
Heiko Becker
c478a0c0fc GIT_SILENT Update Appstream for new release
(cherry picked from commit 11ebe5316f)
2025-06-02 23:46:47 +02:00
l10n daemon script
fe60a08817 GIT_SILENT Sync po/docbooks with svn 2025-06-02 01:39:15 +00:00
James Graham
dec5369a8f Actually position the view at the end
It turns out that for whatever reason ListView.posiitionViewAtEnd() ignores any whitespace. This means that when we use the function it goes to the last delegate. This is no good as we have some padding at the bottom to make space for the typing indicator.

So the fix for this is stupid and involves adding a "spacer" delegate to the timeline beginning model which is completely invisible but qml see as a delegate so we can both leave the space and properly position the view at the end.

BUG: 501075
2025-06-01 10:37:30 +01:00
Joshua Goins
5ec0b9393e Fix showPassiveNotification call in Account Editor
I hate this thing, I think the standalone account editor was fine, but
it didn't work when I went through account settings. Now that should be
fixed.
2025-06-01 04:51:18 +02:00
l10n daemon script
3d8f724bb1 GIT_SILENT Sync po/docbooks with svn 2025-06-01 01:44:08 +00:00
Joshua Goins
bc7a663f92 Prevent opening the non-existent "Home" space in your web browser
I carved out an exception for "DM", but we need it for "Home" too.

BUG: 504842
2025-05-30 14:43:10 +02:00
l10n daemon script
90369e927f GIT_SILENT Sync po/docbooks with svn 2025-05-30 01:42:16 +00:00
Joshua Goins
034cad79c7 Fix the code block handling for our custom Markdown syntax
There were some cases that was hit that revealed some mistakes in the
code block checking code, which is now fixed. Basically, we just needed
to make sure the indices were updated at the right times. I also took
some time to clean up some of the comments, and magic numbers used here.

A new test case was added that was failing before in real-world testing.
2025-05-29 07:39:13 -04:00
Joshua Goins
5c8ba7e29e Fix space switching on mobile
We forgot to connect to onTapped on the touchscreen-specific TapHandler.
I also needed to carve out a specific case for DMs so it doesn't try to
resolve the non-existent "DM" resource.
2025-05-29 06:04:11 -04:00
l10n daemon script
bf1dc00338 GIT_SILENT Sync po/docbooks with svn 2025-05-29 01:43:28 +00:00
l10n daemon script
54f7fd08cb GIT_SILENT Sync po/docbooks with svn 2025-05-28 01:41:22 +00:00
l10n daemon script
e7afa07c84 GIT_SILENT Sync po/docbooks with svn 2025-05-27 01:39:20 +00:00
l10n daemon script
e196ef03d6 GIT_SILENT Sync po/docbooks with svn 2025-05-26 01:40:44 +00:00
l10n daemon script
0fa452ca04 GIT_SILENT Sync po/docbooks with svn 2025-05-25 01:53:51 +00:00
Joshua Goins
15b625a3f8 Move certain internal room information to an Advanced settings page
This has (as I've seen) confuse some users because we put this
information front and center. The internal room ID and it's version
isn't relevant for 99% of users, especially since most of them don't
even have permission to touch these.

Instead, let's do what Element smartly does and put it under an Advanced
page you have to intentionally find. This also has the knock-on effect
of propping up the importance of room aliases, which now appear higher
in the general settings.
2025-05-24 17:18:06 -04:00
Joshua Goins
e2427c0683 Improve the general room information form card
We can remove a lot of duplicative labeling here: "Room Information",
"Room Name", "RoomTopic", etc. The "Save" button now looks like a proper
form card element, like it does in the account editor.
2025-05-24 17:16:52 -04:00
Joshua Goins
a0e8039d92 Give NeoChatRoom the hidden filter for last event cache
Otherwise we keep state events and other unwanted events in the cache
(if the user turned these off.)
2025-05-24 17:02:32 -04:00
Joshua Goins
2f87b1f398 Overhaul how NeoChat restores rooms on space switch
I'm pretty unhappy with the current state, almost no other chat
application (including other Matrix clients) work like this. The biggest
annoyance is opening *other* spaces will send you to the Space Home -
which is almost never what you want! Even though our Space Home is
awesome :-)

Now when you switch spaces, the last room in that space is stored and
will be restored. This also allows us simplify some of the
"open the last room" code. I made sure that this works for the two
special spaces "Home" and "Friends" so everyone can take advantage of
this better UX.

Since this is constantly-changing-state and the worst that can happen is
the wrong room opening on launch, I didn't bother migrating the existing
setting. We'll have to kill it eventually anyway when we make it
account-specific!
2025-05-24 16:54:18 -04:00
Joshua Goins
86fd2e8e1e Improve the UX for rooms that don't have a canonical alias
This was discovered none other than Bug Catcher Nate, while in the TWiKS
room. That room doesn't have a canonical alias set (yet) and that
exposed some UX problems around aliases in NeoChat.

First, the non-canonical alias isn't shown in the info drawer despite
being the only alias available. This is something that Element actually
does, and now NeoChat does too.

Second, NeoChat will try to copy the room's internal Matrix ID (which is
not that great) to the clipboard because it looks for the canonical
alias. Surprisingly, Element also does this but now NeoChat doesn't.
2025-05-24 09:55:52 -04:00
Joshua Goins
4167f55ad8 Remove debug check for GlobalMenu
I accidentally included this in the commit, and it passed through
review, oops. Amends 76919a13b8.
2025-05-24 09:44:50 -04:00
Joshua Goins
76919a13b8 Fix GlobalMenu initialization on other platforms
Amends b5fcad3db0, we need a
ShareAction-like stub for GlobalMenu for this to work again.
2025-05-24 10:52:20 +02:00
l10n daemon script
fe0f159490 GIT_SILENT Sync po/docbooks with svn 2025-05-24 01:40:16 +00:00
Tobias Fella
4527a6399e Add QHttpServer-based mock server
This should allow for creating tests more easily than the python-based server, since we can poke at it from C++ code.
The idea is that each test creates the things it needs (rooms in a certain state, etc) programmatically instead of through
the json files we use for the other tests. This allows us to adapt the test data to each test as needed, without having to copy it around a lot.
2025-05-23 09:48:39 +02:00
l10n daemon script
b8ff75c5ce GIT_SILENT Sync po/docbooks with svn 2025-05-23 01:42:24 +00:00
l10n daemon script
049e8af8b2 GIT_SILENT Sync po/docbooks with svn 2025-05-22 01:41:30 +00:00
Joshua Goins
8ce2386cc9 Restore the text in location component 2025-05-21 16:30:52 -04:00
Joshua Goins
5239040d9c Set the spacing for the hover buttons on code component 2025-05-21 16:26:58 -04:00
Joshua Goins
c5eb7578b7 Improve the buttons for the location component
Now they are icon-only, the "Open Externally" icon has changed, and a
 fullscreen button has been added.
2025-05-21 16:26:58 -04:00
Joshua Goins
76ce44230d Get rid of the latitude/longitude text in the LocationComponent
This also fixes this component not loading.
2025-05-21 16:26:58 -04:00
Joshua Goins
4a20371b87 Make sure the MessageContentModel starts at UserRole
If it doesn't, then our roles start filling up the other Qt ones such as
AccessibleDescription. Instead we need to start UserRole (except for
DisplayRole of course.)
2025-05-21 16:26:58 -04:00
Joshua Goins
9a76c30aaf Remove the unused msgTypeToString function
Not sure why it's here, I just came across it and it's unused.
2025-05-21 16:26:58 -04:00
Joshua Goins
9cba57368e Don't refer to everything as an image in the file context menu
Instead, let's change the text based on the message component type.
2025-05-21 16:26:58 -04:00
Joshua Goins
d895f8d771 Fix right-clicking delegates again
I accidentally broke this while trying to dance around the two new
overlapping TapHandlers, but this didn't work. What ended up happening
is that you couldn't right-click non-message delegates anymore (such as
images, files, etc.)

I *still* couldn't figure out how to get overlapping TapHandlers to
work, so I just switched to a MouseArea instead. This makes everything
work as expected now.
2025-05-21 16:26:58 -04:00
Joshua Goins
de8c9f4878 Add padding around file progress indicator, move it to the center
Before it stuck to the top-left corner, and looked bad. Now it's
properly centered inside of the chat bar.
2025-05-21 16:24:34 -04:00
Joshua Goins
f029cf842a Add duration to voice message delegate
Otherwise you have to download the audio file before you can tell how
large it is.
2025-05-21 16:23:17 -04:00
James Graham
6ce9b77b3c Set m_quickActionIncubating and m_compactBackgroundIncubating to avoid heap-use-after-free 2025-05-21 10:08:55 +01:00
l10n daemon script
5a60c6ec67 GIT_SILENT Sync po/docbooks with svn 2025-05-21 01:41:56 +00:00
James Graham
a1ca768711 Use the forget function to leave a room everywhere
Use the forget function to leave a room everywhere this is both for consistency and to reduce dependencies. This way no dependency on RoomManager is required to leave a room and since in all cases they have an object they can just call the function.
2025-05-20 17:35:34 +01:00
l10n daemon script
f0d2c19393 GIT_SILENT Sync po/docbooks with svn 2025-05-20 01:39:59 +00:00
Carl Schwan
67db05a0c3 Adapt API to ConvergentContextMenu change
Use close instead of using internal menuItem
2025-05-19 18:59:45 +02:00
Ritchie Frodomar
05e932b884 Move typing indicators closer to messages
This merge request addresses a minor papercut in NeoChat's layout when using a screen magnifier.

If you are zoomed in on a conversation, typing indicators are generally invisible. This merge request moves them to align with actual messages, making them more visible to screen magnifier users like myself. That way, you can do the text chat equivalent of waiting for someone else to finish talking before you ramble on.

### Before
![Screenshot of NeoChat in a test conversation. A user is typing. The typing pane is very far off to the left, not visible when zoomed in.](https://cdn.acidiclight.dev/images/2025/04/03/31d836b18875.png)

### After
![Screenshot of the typing indicator in a conversation. The typing pane now aligns with the chatbar and messages.](https://cdn.acidiclight.dev/images/2025/04/03/870ace40ce86.png)
2025-05-19 12:07:02 -04:00
l10n daemon script
763713cd32 GIT_SILENT Sync po/docbooks with svn 2025-05-19 01:42:11 +00:00
Joshua Goins
2687448212 Add header to UserMenu for mobile which has the user info
To give a little bit more context, and it also matches the message
context menu which does the same.
2025-05-18 20:00:21 -04:00
Joshua Goins
d059195e92 Add user menu that is opened via right-clicking/long-tapped on avatar
We can un-clutter our message context menu, which we had to share
with user actions. (Even though we only had one so far.) I added one new
user-specific action which allows you to quickly mention the user in
chat. Otherwise you would've had to copy their username or use the
completion menu.

It's convergent on mobile, it still has the hover indicator and it also
is available through the AuthorComponent.

BUG: 486252
2025-05-18 20:00:20 -04:00
James Graham
6ef7acc8e5 Remove any dependencies on App from the spaces module.
Remove any dependencies on App from the spaces module. This requires moving some dialogs either to spaces, or libneochat if they're used more generically.
2025-05-18 14:38:57 +01:00
l10n daemon script
2cb89807ef GIT_SILENT Sync po/docbooks with svn 2025-05-18 01:40:43 +00:00
Joshua Goins
b5fcad3db0 Fix multiple Global menu issues
See individual commits. When backporting, I'll create a separate patchset just for the we-broke-something-bugs without the strings.
2025-05-17 21:21:38 -04:00
Joshua Goins
d3fd441c88 Improve the "Create a Room/Space" and "Select Existing Room" dialog
First of all, these are now all separate dialogs instead of shoving all
of these functions (which are only marginally related) into one single
dialog. We also convert these to in-app Kirigami Dialogs, which look a
bit nicer. I also touched up the UX in some places, such as adding
descriptions which were previously available to translators. I also hid
some not oft used options like setting a topic, which almost nobody does
before creating a room/space.
2025-05-17 21:09:43 -04:00
Joshua Goins
e9568b50fc If the message body is empty, say so
Normally if a malformed event is empty it will just be empty space - but
that looks buggy. Instead, we can add a message saying "This event does
not have any content."

BUG: 494093
2025-05-17 21:09:25 -04:00
James Graham
e7040a518a Create a new module for the room info drawer QML.
Create a new module for the room info drawer QML. This also requires moving some QML to LibNeoChat common with other modules. Finally all QML in roominfo is modifed to not depend on app.
2025-05-17 14:27:38 +01:00
297 changed files with 78446 additions and 57588 deletions

View File

@@ -2,7 +2,5 @@
; SPDX-License-Identifier: CC0-1.0
[BlueprintSettings]
kde/frameworks/extra-cmake-modules.version=master
kde/unreleased/kirigami-addons.version=master
kde/applications/neochat.packageAppx=True
libs/qt.qtMajorVersion=6

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "6.8",
"runtime-version": "6.9",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [
@@ -149,27 +149,6 @@
],
"builddir": true
},
{
"name": "qcoro",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DQCORO_BUILD_EXAMPLES=OFF",
"-DBUILD_TESTING=OFF"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.11.0.tar.gz",
"sha256": "9942c5b4c533192f6c5954dc6d10178b3829075e6a621b67df73f0a4b74d8297",
"x-checker-data": {
"type": "anitya",
"project-id": 236236,
"stable-only": true,
"url-template": "https://github.com/danvratil/qcoro/archive/refs/tags/v$version.tar.gz"
}
}
]
},
{
"name": "kunifiedpush",
"buildsystem": "cmake-ninja",

View File

@@ -8,13 +8,13 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "07")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION_MINOR "08")
set(RELEASE_SERVICE_VERSION_MICRO "3")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
set(KF_MIN_VERSION "6.12")
set(KF_MIN_VERSION "6.16")
set(QT_MIN_VERSION "6.5")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
@@ -164,6 +164,7 @@ endif()
if(ANDROID)
find_package(Sqlite3)
set(BUILD_TESTING FALSE)
endif()
ki18n_install(po)
@@ -178,7 +179,7 @@ add_definitions(-DQT_NO_FOREACH)
add_subdirectory(src)
if (BUILD_TESTING)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test HttpServer)
add_subdirectory(autotests)
# add_subdirectory(appiumtests)
if (NOT ANDROID)

View File

@@ -3,6 +3,10 @@
enable_testing()
add_library(neochat_server STATIC server.cpp)
target_link_libraries(neochat_server PUBLIC Qt::HttpServer QuotientQt6)
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
ecm_add_test(
@@ -85,6 +89,6 @@ ecm_add_test(
ecm_add_test(
actionstest.cpp
LINK_LIBRARIES neochat Qt::Test
LINK_LIBRARIES neochat Qt::Test neochat_server
TEST_NAME actionstest
)

View File

@@ -6,9 +6,11 @@
#include <QSignalSpy>
#include <QVariantList>
#include "accountmanager.h"
#include "chatbarcache.h"
#include "models/actionsmodel.h"
#include "server.h"
#include "testutils.h"
using namespace Quotient;
@@ -21,10 +23,12 @@ class ActionsTest : public QObject
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
NeoChatRoom *room = nullptr;
void expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message);
Server server;
private Q_SLOTS:
void initTestCase();
void testActions();
@@ -34,8 +38,23 @@ private Q_SLOTS:
void ActionsTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-min-sync.json"));
Connection::setRoomType<NeoChatRoom>();
server.start();
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
auto accountManager = new AccountManager(true);
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
connection = accountManager->accounts()->front();
auto roomId = server.createRoom(u"@user:localhost:1234"_s);
server.inviteUser(roomId, u"@invited:example.com"_s);
server.banUser(roomId, u"@banned:example.com"_s);
server.joinUser(roomId, u"@example:example.com"_s);
QSignalSpy syncSpy(connection, &Connection::syncDone);
// We need to wait for two syncs, as the next one won't have the changes yet
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
QVERIFY(room);
}
void ActionsTest::testActions_data()
@@ -90,7 +109,7 @@ static ActionsModel::Action findAction(const QString &name)
void ActionsTest::expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message)
{
auto action = findAction(actionName);
QSignalSpy spy(room, &TestUtils::TestRoom::showMessage);
QSignalSpy spy(room, &NeoChatRoom::showMessage);
auto result = action.handle(args, room, nullptr);
auto expected = QVariantList {type, message};
auto signal = spy.takeFirst();
@@ -106,14 +125,26 @@ void ActionsTest::testInvite()
QCOMPARE(room->memberState(u"@banned:example.com"_s), Membership::Ban);
expectMessage(u"invite"_s, connection->userId(), MessageType::Positive, u"You are already in this room."_s);
QCOMPARE(room->memberState(connection->userId()), Membership::Join);
expectMessage(u"invite"_s, u"@example:example.org"_s, MessageType::Information, u"@example:example.org is already in this room."_s);
QCOMPARE(room->memberState(u"@example:example.org"_s), Membership::Join);
expectMessage(u"invite"_s, u"@example:example.com"_s, MessageType::Information, u"@example:example.com is already in this room."_s);
QCOMPARE(room->memberState(u"@example:example.com"_s), Membership::Join);
QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Leave);
expectMessage(u"invite"_s, u"@user:example.com"_s, MessageType::Positive, u"@user:example.com was invited into this room."_s);
//TODO mock server, wait for invite state to change
//TODO QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Invite);
QSignalSpy spy(room, &NeoChatRoom::changed);
QVERIFY(spy.wait());
auto tries = 0;
while (room->memberState(u"@user:example.com"_s) != Membership::Invite) {
QVERIFY(spy.wait());
tries += 1;
if (tries > 3) {
QVERIFY(false);
}
}
QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Invite);
}
QTEST_MAIN(ActionsTest)

View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDNTCCAh2gAwIBAgIUXbyWfTfcvVLrVB1qx36pW/7IkwMwDQYJKoZIhvcNAQEL
BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAgFw0yNDEyMjQxNTAxMDNaGA8yNTcyMDcy
NDE1MDEwM1owQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEc
MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKlxZ540TQ1uUDAR7ZJ9ue0PzcD2dPmblIIddyekvZS59V7X
drhamclXpHE2EelR87Sexst0BaHH/jmrHwxCtwbeXHZ8ueJHkGHJ5DLZCCiwfG+Q
gml7wlSXxXz37vie2tdlZh2yJSM8yvLAYceHb2zOskaGvul7ZITIS0JrPc3o6VZk
+MYGkYtA2JfUsv3jH4oQbxOf7RXqhWNAXbB+3hlwRBwMIdyoBNK6YS9QSrTeS9jj
UqgO5QmaQZOVvpaPf1Y/rHHLd2Qa6+a/cCJ1sr2biagb75AihpQFsK/oy6D1PP70
zTe7hPWn/efEpmtCV7CQ8ti4cRu0Kjy0T8grtCsCAwEAAaMhMB8wHQYDVR0OBBYE
FIFlylzwADNLfgTDNkhFeFelaEDxMA0GCSqGSIb3DQEBCwUAA4IBAQBQ2rw4GLIU
v+GY7Qru9LttkrQPd2bZXKxDMd/jT+wjmMVtqS4MAsCuDYwaYLjU1aWyqy0mN+lY
A17kD0VjBNBy45sYqkZveY0ks8mCScBemtrIDmjz2tiueecBIEASwEPBOZgv5/MV
cz864FiChF+2r8Zl8bhycGy9DEpRjzYKvIQWSDHQ3zpuh3iBnjfoieLHWX2kKCpk
ouS3V6485rHNCWsZT5IcCwfBFQkOuWRJpIazpz4AfwZh1TK9+bgiKA5EyZjSNrKw
xGQSpMSTRQMB0/FOCL/AixhN9unVFUViqUcdtSfoHE1VyBHv9kDT/cYms/Xl4B0t
/ZSQJ/D/Km1+
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCpcWeeNE0NblAw
Ee2SfbntD83A9nT5m5SCHXcnpL2UufVe13a4WpnJV6RxNhHpUfO0nsbLdAWhx/45
qx8MQrcG3lx2fLniR5BhyeQy2QgosHxvkIJpe8JUl8V89+74ntrXZWYdsiUjPMry
wGHHh29szrJGhr7pe2SEyEtCaz3N6OlWZPjGBpGLQNiX1LL94x+KEG8Tn+0V6oVj
QF2wft4ZcEQcDCHcqATSumEvUEq03kvY41KoDuUJmkGTlb6Wj39WP6xxy3dkGuvm
v3AidbK9m4moG++QIoaUBbCv6Mug9Tz+9M03u4T1p/3nxKZrQlewkPLYuHEbtCo8
tE/IK7QrAgMBAAECggEAH9qmeKrra2F4KLlOGNKS//qPGz4Z+ozhi95/NpA1Zb7Z
3pUSCBFcROo5i2D3WA4kiymoRLpQjrv60puVcCggoWVvK4VCKsR6Y6/hOx/q9T9M
fWrE4ZC3FVEc+uPfZJT0nja9TkrdyXSV0LITD8Ap1eI7yJ9vR5R/bqj64QcpLMrU
QeoQIy1oTMR+qdjj33duyRwBZU3Yf8FRB2iW6OILZ8hzFo1jngec7dph9a1RK4e0
mEPdc9ywsKlDM7P0Y7zdmjar5XtQn87GiwNhz23f1fzCC2axLtOW0Xm4e4Qumehb
WrIi6Vfq8IWMglU7QrBJ7iR0Ls+XoKA5GxomV2IJZQKBgQDoIkOl5YGPQ3iGR+WK
e5/2Ml4G/uURzYiOlzSsyfoPXyO4EI2BJd5HkH+EvfgRx4xKkxUZRJdzR7llYPl8
BFYcFitvhO8SbD0mNAB5YW7f+3v1pgEN2umzoKd389Zx5WqTZ7YB1VG5RN/Q1JJL
2JM0Xgamq2vNtx3roRPxDBeW7QKBgQC63R/bmACJbgIzfaVBX4Zie3NQG0/Hf+gF
LnBwUmQDZOR7MY+kSiIUVMn3NuZRiCSCFBVwApruyK8r535JCibTVm5PWjvhFddY
LgaPOCKGlm9TLScjoH1pErYgG3uJ4nXeRfXhg4mco6EkrC7RzQywrd0VDoqpuc1Y
EKfEsYk8dwKBgE+mSh3nNOBKX1V73+f3aTiZqaeu2DyWkG+UtE9BclrJ40Cp9VPG
AZH+o7KRWEgJdzqzYv7riSfWCWgesRv7hOxYMwktzLY+i3DLUQpVAy05ZhwwnJX7
ckrfKfc/pGoqNLplUI8qecMfPciy14vMwR2r0Y5orTHFzi9mcqg35PQ1AoGAW2LX
OLq+0HdHhk0Va8I+450CSRQCUUvhed87SANTPEG0Z/dWC3/h6NWKrGdh/k+5oxAV
Z+EuSkdFPBCLt0bKtCKZ8h7sF+lplotz08kdQXsC2MfFU2wiySdIgK1QHp/tCxZl
6LM+sqdnoJrAjwRcB3AQJkMlV1ox7ba/hbdZqYMCgYBS6+JUXSSASpm5ZHd32a8m
xwryEZ7H6Hek6lvMHdxmwoKat5dCavxw64nrtyeeGZpg1W3zLLyamF9x/8kMyr6y
KKvtBfJ5sCvAbt80o9Pbs6R3yDB3AKiD3s3PQK7lol1nhE/8IbsF2r8JEQVcYd/k
oBzkl7MrMyLhhaCqSxwqQQ==
-----END PRIVATE KEY-----

View File

@@ -130,7 +130,8 @@ void EventHandlerTest::timeString()
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, true),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toString(u"hh:mm"_s));
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s),
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::LocalTime)).toString(u"hh:mm"_s));
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
QCOMPARE(room->pendingEvents().size(), 1);

235
autotests/server.cpp Normal file
View File

@@ -0,0 +1,235 @@
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "server.h"
#include <QFile>
#include <QHttpServer>
#include <QHttpServerResponder>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QSslCertificate>
#include <QSslKey>
#include <QSslServer>
#include <QUuid>
#include <Quotient/networkaccessmanager.h>
using namespace Qt::Literals::StringLiterals;
QString generateEventId()
{
return u"$"_s + QString::fromLatin1(QCryptographicHash::hash(QUuid::createUuid().toString().toLatin1(), QCryptographicHash::Sha1).toBase64());
}
QString generateRoomId()
{
return u"!%1:localhost:1234"_s
.arg(QString::fromLatin1(QCryptographicHash::hash(QUuid::createUuid().toString().toLatin1(), QCryptographicHash::Sha1).toBase64()))
.replace(u'/', QChar());
}
Server::Server()
{
}
void Server::start()
{
QObject::connect(Quotient::NetworkAccessManager::instance(),
&QNetworkAccessManager::sslErrors,
Quotient::NetworkAccessManager::instance(),
[](QNetworkReply *reply) {
reply->ignoreSslErrors();
});
m_server.route(u"/.well-known/matrix/client"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
responder.write(QJsonDocument(QJsonObject{
{u"m.homeserver"_s, QJsonObject{{u"base_url"_s, u"https://localhost:1234"_s}}},
}),
QHttpServerResponder::StatusCode::Ok);
});
m_server.route(u"/_matrix/client/versions"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
responder.write(QJsonDocument(QJsonObject{
{u"versions"_s,
QJsonArray{
u"v1.0"_s,
u"v1.1"_s,
u"v1.2"_s,
u"v1.3"_s,
u"v1.4"_s,
u"v1.5"_s,
u"v1.6"_s,
u"v1.7"_s,
u"v1.8"_s,
u"v1.9"_s,
u"v1.10"_s,
u"v1.11"_s,
u"v1.12"_s,
u"v1.13"_s,
}},
}),
QHttpServerResponder::StatusCode::Ok);
});
m_server.route(u"/_matrix/client/v3/capabilities"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
responder.write(
QJsonDocument(QJsonObject{{u"capabilities"_s,
QJsonObject{
{u"m.room_versions"_s, QJsonObject{{u"m.available"_s, QJsonObject{{u"1"_s, u"stable"_s}}}, {u"default"_s, u"1"_s}}},
}}}),
QHttpServerResponder::StatusCode::Ok);
});
m_server.route(u"/_matrix/client/v3/account/whoami"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
responder.write(QJsonDocument(QJsonObject{
{u"device_id"_s, u"device_id_1234"_s},
{u"user_id"_s, u"@user:localhost:1234"_s},
}),
QHttpServerResponder::StatusCode::Ok);
});
m_server.route(u"/_matrix/client/v3/login"_s, QHttpServerRequest::Method::Post, [](QHttpServerResponder &responder) {
// TODO
// if data["identifier"]["user"] != "user" or data["password"] != "1234":
// abort(403)
responder.write(QJsonDocument(QJsonObject{
{u"access_token"_s, u"token_login"_s},
{u"device_id"_s, u"device_1234"_s},
{u"user_id"_s, u"@user:localhost:1234"_s},
}),
QHttpServerResponder::StatusCode::Ok);
});
m_server.route(u"/_matrix/client/v3/login"_s, QHttpServerRequest::Method::Get, [](QHttpServerResponder &responder) {
responder.write(QJsonDocument(QJsonObject{
{u"flows"_s, QJsonArray{QJsonObject{{u"type"_s, u"m.login.password"_s}}}},
}),
QHttpServerResponder::StatusCode::Ok);
});
m_server.route(u"/_matrix/client/v3/rooms/<arg>/invite"_s,
QHttpServerRequest::Method::Post,
[this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) {
m_invitedUsers[roomId] += QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString();
responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok);
});
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) {
QMap<QString, QJsonArray> stateEvents;
for (const auto &[roomId, matrixId] : m_roomsToCreate) {
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, matrixId},
{u"state_key"_s, QString()},
{u"type"_s, u"m.room.create"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, matrixId},
{u"state_key"_s, matrixId},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
}
m_roomsToCreate.clear();
for (const auto &roomId : m_invitedUsers.keys()) {
const auto &values = m_invitedUsers[roomId];
for (const auto &value : values) {
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, u"@user:localhost:1234"_s},
{u"state_key"_s, value},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
}
}
m_invitedUsers.clear();
for (const auto &roomId : m_bannedUsers.keys()) {
const auto &values = m_bannedUsers[roomId];
for (const auto &value : values) {
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, u"@user:localhost:1234"_s},
{u"state_key"_s, value},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
}
}
m_bannedUsers.clear();
for (const auto &roomId : m_joinedUsers.keys()) {
const auto &values = m_joinedUsers[roomId];
for (const auto &value : values) {
stateEvents[roomId] += QJsonObject{
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
{u"event_id"_s, generateEventId()},
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
{u"room_id"_s, roomId},
{u"sender"_s, u"@user:localhost:1234"_s},
{u"state_key"_s, value},
{u"type"_s, u"m.room.member"_s},
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
};
}
}
m_joinedUsers.clear();
QJsonObject rooms;
for (const auto &roomId : stateEvents.keys()) {
rooms[roomId] = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}}};
}
responder.write(QJsonDocument(QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}}), QHttpServerResponder::StatusCode::Ok);
});
QSslConfiguration config;
QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s);
key.open(QFile::ReadOnly);
config.setPrivateKey(QSslKey(&key, QSsl::Rsa));
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front());
m_sslServer.setSslConfiguration(config);
if (!m_sslServer.listen(QHostAddress::LocalHost, 1234) || !m_server.bind(&m_sslServer)) {
qFatal() << "Server failed to listen on a port.";
return;
} else {
qWarning() << "Server listening";
}
}
QString Server::createRoom(const QString &matrixId)
{
auto roomId = generateRoomId();
m_roomsToCreate += {roomId, matrixId};
return roomId;
}
void Server::inviteUser(const QString &roomId, const QString &matrixId)
{
m_invitedUsers[roomId] += matrixId;
}
void Server::banUser(const QString &roomId, const QString &matrixId)
{
m_bannedUsers[roomId] += matrixId;
}
void Server::joinUser(const QString &roomId, const QString &matrixId)
{
m_joinedUsers[roomId] += matrixId;
}

33
autotests/server.h Normal file
View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include <QHttpServer>
#include <QSslServer>
class Server
{
public:
Server();
void start();
/**
* Create a room and place the user with id matrixId in it.
* Returns the room's id
*/
QString createRoom(const QString &matrixId);
void inviteUser(const QString &roomId, const QString &matrixId);
void banUser(const QString &roomId, const QString &matrixId);
void joinUser(const QString &roomId, const QString &matrixId);
private:
QHttpServer m_server;
QSslServer m_sslServer;
QHash<QString, QList<QString>> m_invitedUsers;
QHash<QString, QList<QString>> m_bannedUsers;
QHash<QString, QList<QString>> m_joinedUsers;
QList<std::pair<QString, QString>> m_roomsToCreate;
};

View File

@@ -61,6 +61,7 @@ private Q_SLOTS:
void receiveRichStrikethrough();
void receiveRichtextIn();
void receiveRichMxcUrl();
void receiveRichPlainUrl_data();
void receiveRichPlainUrl();
void receiveRichEdited_data();
void receiveRichEdited();
@@ -264,6 +265,8 @@ void TextHandlerTest::sendCustomTags_data()
QTest::newRow("inside code block spoiler") << u"```||apple||```"_s << u"<code>||apple||</code>"_s;
QTest::newRow("outside code block spoiler") << u"||apple|| ```||banana||``` ||pear||"_s
<< u"<span data-mx-spoiler>apple</span> <code>||banana||</code> <span data-mx-spoiler>pear</span>"_s;
QTest::newRow("complex spoiler") << u"Between `formFactor == Horizontal||Vertical` and `location == top||left||bottom||right`"_s
<< u"Between <code>formFactor == Horizontal||Vertical</code> and <code>location == top||left||bottom||right</code>"_s;
// strikethrough
QTest::newRow("incomplete strikethrough") << u"~~test"_s << u"~~test"_s;
@@ -448,6 +451,32 @@ void TextHandlerTest::receiveRichMxcUrl()
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
}
void TextHandlerTest::receiveRichPlainUrl_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
// This is an actual link that caused trouble which is why it's so long. Keeping
// so we can confirm consistent behaviour for complex urls.
QTest::addRow("link 1")
<< u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s
<< u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was broken into 3 but needs to
// be just single link.
QTest::addRow("link 2")
<< u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s
<< u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
QTest::addRow("email") << uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
<< uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
QTest::addRow("mxid")
<< u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s
<< u"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>"_s;
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s << u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
}
/**
* For when your rich input string has a plain text url left in.
*
@@ -456,46 +485,13 @@ void TextHandlerTest::receiveRichMxcUrl()
*/
void TextHandlerTest::receiveRichPlainUrl()
{
// This is an actual link that caused trouble which is why it's so long. Keeping
// so we can confirm consistent behaviour for complex urls.
const QString testInputStringLink1 =
u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
const QString testOutputStringLink1 =
u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was been broken into 3 but needs to
// be just single link.
const QString testInputStringLink2 = u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s;
const QString testOutputStringLink2 =
u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
QString testInputStringEmail = uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s;
QString testOutputStringEmail = uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
QString testInputStringMxId = u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s;
QString testOutputStringMxId =
u"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>"_s;
QString testInputStringMxIdWithPrefix = u"a @user:kde.org b"_s;
QString testOutputStringMxIdWithPrefix = u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
QFETCH(QString, input);
QFETCH(QString, output);
TextHandler testTextHandler;
testTextHandler.setData(testInputStringLink1);
testTextHandler.setData(input);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink1);
testTextHandler.setData(testInputStringLink2);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink2);
testTextHandler.setData(testInputStringEmail);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringEmail);
testTextHandler.setData(testInputStringMxId);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
testTextHandler.setData(testInputStringMxIdWithPrefix);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxIdWithPrefix);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), output);
}
void TextHandlerTest::receiveRichEdited_data()

View File

@@ -57,7 +57,7 @@ void TimelineMessageModelTest::switchEmptyRoom()
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s);
QSignalSpy spy(model, SIGNAL(roomChanged()));
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *)));
QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom);
@@ -77,7 +77,7 @@ void TimelineMessageModelTest::switchSyncedRoom()
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
QSignalSpy spy(model, SIGNAL(roomChanged()));
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *)));
QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom);
@@ -208,7 +208,7 @@ void TimelineMessageModelTest::idToRow()
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
model->setRoom(room);
QCOMPARE(model->eventIdToRow(u"$153456789:example.org"_s), 0);
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0);
}
void TimelineMessageModelTest::cleanup()

View File

@@ -41,6 +41,7 @@
<name xml:lang="pl">NeoChat</name>
<name xml:lang="pt">NeoChat</name>
<name xml:lang="pt-BR">NeoChat</name>
<name xml:lang="ro">NeoChat</name>
<name xml:lang="ru">NeoChat</name>
<name xml:lang="sa">नवचैट्</name>
<name xml:lang="sk">NeoChat</name>
@@ -49,7 +50,6 @@
<name xml:lang="ta">நியோச்சாட்</name>
<name xml:lang="tr">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-TW">NeoChat</name>
<summary>Chat on Matrix</summary>
@@ -75,6 +75,8 @@
<summary xml:lang="nl">Chat op Matrix</summary>
<summary xml:lang="nn">Prat med via Matrix</summary>
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
<summary xml:lang="pt-BR">Bate-papo na Matrix</summary>
<summary xml:lang="ro">Discutați pe Matrix</summary>
<summary xml:lang="ru">Общение в Matrix</summary>
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
<summary xml:lang="sl">Klepet na Matrixu</summary>
@@ -82,7 +84,6 @@
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
<summary xml:lang="uk">Спілкування у Matrix</summary>
<summary xml:lang="x-test">xxChat on Matrixxx</summary>
<summary xml:lang="zh-TW">在 Matrix 上聊天</summary>
<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>
@@ -109,16 +110,17 @@
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
<p xml:lang="pt-BR">O NeoChat é um aplicativo de bate-papo que permite que você aproveite ao máximo a rede Matrix. Ele oferece uma maneira segura de enviar mensagens de texto, vídeos e arquivos de áudio para sua família, colegas e amigos.</p>
<p xml:lang="ro">NeoChat e o aplicație de discuții ce vă ajută să profitați din plin de rețeaua Matrix. Aceasta oferă o modalitate sigură de a trimite mesaje textuale, videoclipuri și fișiere audio familiei, colegilor și prietenilor.</p>
<p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p>
<p xml:lang="sa">NeoChat इति एकं गपशप-अनुप्रयोगं यत् भवान् Matrix-जालस्य पूर्णं लाभं ग्रहीतुं शक्नोति । एतत् भवन्तं भवतः परिवाराय, सहकारिभ्यः, मित्रेभ्यः च पाठसन्देशान्, भिडियो, श्रव्यसञ्चिकाः च प्रेषयितुं सुरक्षितं मार्गं प्रदाति ।</p>
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
<p xml:lang="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="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>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-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>
@@ -142,16 +144,17 @@
<p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p>
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
<p xml:lang="pt-BR">O NeoChat pretende ser um aplicativo completo para a especificação Matrix. Dessa forma, tudo na especificação estável atual, com as notáveis exceções de VoIP, tópicos e alguns aspectos da criptografia de ponta a ponta, é suportado. Há algumas outras pequenas omissões devido ao fato de a especificação Matrix estar em constante evolução, mas o objetivo continua sendo fornecer suporte eventual para toda a especificação.</p>
<p xml:lang="ro">NeoChat vrea să fie o aplicație completă pentru specificațiile Matrix. Astfel, susține tot ce se găsește acum în specificațiile stabile cu excepția VoIP, a firelor de discuții, și a unor părți din criptarea punct-la-punct. Sunt și câteva omisiuni minore din cauza faptului că specificația Matrix evoluează continuu, dar scopul rămâne acela de a implementa întreaga specificație.</p>
<p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p>
<p xml:lang="sa">NeoChat इत्यस्य उद्देश्यं Matrix विनिर्देशस्य कृते पूर्णतया विशेषतायुक्तः अनुप्रयोगः भवितुम् अस्ति । यथा तथा वर्तमानस्थिरविनिर्देशे सर्वं VoIP इत्यस्य उल्लेखनीयअपवादैः सह, थ्रेड्स तथा च End-to-End Encryption इत्यस्य केचन पक्षाः समर्थिताः सन्ति । अन्ये कतिचन लघु लोपाः सन्ति यतोहि Matrix spec निरन्तरं विकसितः अस्ति परन्तु उद्देश्यं सम्पूर्ण spec कृते अन्ततः समर्थनं प्रदातुं अवशिष्टम् अस्ति</p>
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
<p xml:lang="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="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>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-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>
@@ -175,6 +178,8 @@
<p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p>
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
<p xml:lang="pt-BR">Devido à natureza do desenvolvimento da especificação Matrix, o NeoChat também suporta diversos recursos instáveis. Atualmente, são eles:</p>
<p xml:lang="ro">Datorită modului de dezvoltare a specificațiilor Matrix, NeoChat susține și numeroase caracteristici nestabile. Acum, acestea sunt:</p>
<p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p>
<p xml:lang="sa">Matrix विनिर्देशविकासस्य प्रकृतेः कारणात् NeoChat अपि अनेकानाम् अस्थिरविशेषतानां समर्थनं करोति । सम्प्रति एते सन्ति :</p>
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
@@ -182,13 +187,12 @@
<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="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>
<ul>
<li>Polls - MSC3381</li>
<li xml:lang="ar">التصويت - MSC3381</li>
<li xml:lang="ca">Enquestes - MSC3381</li>
<li xml:lang="ca-valencia">Enquestes - MSC3381</li>
<li xml:lang="ca">Votacions - MSC3381</li>
<li xml:lang="ca-valencia">Votacions - MSC3381</li>
<li xml:lang="el">Δημοσκοπήσεις - MSC3381</li>
<li xml:lang="en-GB">Polls - MSC3381</li>
<li xml:lang="eo">Enketoj - MSC3381</li>
@@ -209,6 +213,8 @@
<li xml:lang="nn">Avstemmingar  MSC3381</li>
<li xml:lang="pl">Ankiety - MSC3381</li>
<li xml:lang="pt">Inquéritos - MSC3381</li>
<li xml:lang="pt-BR">Enquetes - MSC3381</li>
<li xml:lang="ro">Sondaje - MSC3381</li>
<li xml:lang="ru">Голосования — MSC3381</li>
<li xml:lang="sa">मतदान - MSC3381</li>
<li xml:lang="sl">Polls - MSC3381</li>
@@ -216,7 +222,6 @@
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
<li xml:lang="tr">Anketler — MSC3381</li>
<li xml:lang="uk">Опитування - MSC3381</li>
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
<li xml:lang="zh-TW">投票 - MSC3381</li>
<li>Sticker Packs - MSC2545</li>
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
@@ -242,6 +247,8 @@
<li xml:lang="nn">Klistremerke-pakkar  MSC2545</li>
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
<li xml:lang="pt-BR">Pacotes de Stickers - MSC2545</li>
<li xml:lang="ro">Colecții de abțibilduri - MSC2545</li>
<li xml:lang="ru">Наборы стикеров — MSC2545</li>
<li xml:lang="sa">स्टिकर पैक - MSC2545</li>
<li xml:lang="sl">Sticker Packs - MSC2545</li>
@@ -249,7 +256,6 @@
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
<li xml:lang="tr">Çıkartma Paketleri — 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>Location Events - MSC3488</li>
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
@@ -275,6 +281,8 @@
<li xml:lang="nn">Posisjonshendingar  MSC3488</li>
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
<li xml:lang="pt-BR">Localização de eventos - MSC3488</li>
<li xml:lang="ro">Evenimente de amplasare - MSC3488</li>
<li xml:lang="ru">События местоположения — MSC3488</li>
<li xml:lang="sa">स्थान घटनाएँ - MSC3488</li>
<li xml:lang="sl">Location Events - MSC3488</li>
@@ -282,7 +290,6 @@
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
<li xml:lang="tr">Konum Etkinlikleri — MSC3488</li>
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
<li xml:lang="zh-TW">位置事件 - MSC3488</li>
</ul>
</description>
@@ -344,6 +351,8 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
@@ -351,7 +360,6 @@
<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="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot>
<screenshot type="default">
@@ -380,6 +388,8 @@
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
<caption xml:lang="pt-BR">Descubra novas comunidades com os Espaços Matrix</caption>
<caption xml:lang="ro">Descoperiți comunități noi cu Spații Matrix</caption>
<caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption>
<caption xml:lang="sa">Matrix Spaces इत्यनेन सह नूतनानां समुदायानाम् अन्वेषणं कुर्वन्तु</caption>
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
@@ -387,7 +397,6 @@
<caption xml:lang="ta">மேட்ரிக்ஸு இடங்களின் மூலம் புதிய சமூகங்களைக் கண்டுபிடிக்கலாம்</caption>
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</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>
</screenshot>
<!--
@@ -424,6 +433,8 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
@@ -431,7 +442,6 @@
<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="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot>
<screenshot environment="windows">
@@ -462,6 +472,8 @@
<caption xml:lang="nn">Innloggingsbilete</caption>
<caption xml:lang="pl">Ekran logowania</caption>
<caption xml:lang="pt">Ecrã de autenticação</caption>
<caption xml:lang="pt-BR">Tela de login</caption>
<caption xml:lang="ro">Ecran de autentificare</caption>
<caption xml:lang="ru">Окно входа</caption>
<caption xml:lang="sa">लॉगिन् स्क्रीन</caption>
<caption xml:lang="sl">Prijavni zaslon</caption>
@@ -469,7 +481,6 @@
<caption xml:lang="ta">நுழைவுத் திரை</caption>
<caption xml:lang="tr">Oturum açma ekranı</caption>
<caption xml:lang="uk">Вікно входу</caption>
<caption xml:lang="x-test">xxLogin screenxx</caption>
<caption xml:lang="zh-TW">登入畫面</caption>
</screenshot>
</screenshots>
@@ -477,6 +488,12 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="25.08.3" date="2025-11-06"/>
<release version="25.08.2" date="2025-10-09"/>
<release version="25.08.1" date="2025-09-11"/>
<release version="25.08.0" date="2025-08-14"/>
<release version="25.04.3" date="2025-07-03"/>
<release version="25.04.2" date="2025-06-05"/>
<release version="25.04.1" date="2025-05-08"/>
<release version="25.04.0" date="2025-04-17"/>
<release version="24.12.3" date="2025-03-06"/>

View File

@@ -44,7 +44,6 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
GenericName=Matrix Client
@@ -88,7 +87,6 @@ GenericName[sv]=Matrix-klient
GenericName[ta]=Matrix வாங்கி
GenericName[tr]=Matrix İstemcisi
GenericName[uk]=Клієнт Matrix
GenericName[x-test]=xxMatrix Clientxx
GenericName[zh_CN]=Matrix 客户端
GenericName[zh_TW]=Matrix 用戶端
Comment=Chat on Matrix
@@ -114,6 +112,7 @@ Comment[lv]=Tērzējiet „Matrix“ tīklā
Comment[nl]=Chat op Matrix
Comment[pl]=Rozmawiaj na Matriksie
Comment[pt_BR]=Bate papo na Matrix
Comment[ro]=Discutați pe Matrix
Comment[ru]=Общение в Matrix
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
Comment[sl]=Klepet na Matrixu
@@ -121,7 +120,6 @@ Comment[sv]=Chatta på Matrix
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
Comment[tr]=Matrix üzerinde sohbet edin
Comment[uk]=Спілкування у Matrix
Comment[x-test]=xxChat on Matrixxx
Comment[zh_CN]=在 Matrix 上聊天
Comment[zh_TW]=在 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

6548
po/ro/neochat.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
<!ENTITY % Russian "INCLUDE">
]>
<!--
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<refentry lang="&language;">
<refentryinfo>
<title
>Руководство пользователя NeoChat</title>
<author
><firstname
>Carl</firstname
><surname
>Schwan</surname
> <contrib
>man-страница NeoChat.</contrib
> <email
>carl@carlschwan.eu</email
></author>
<date
>2022-11-01</date>
<releaseinfo
>22.09</releaseinfo>
<productname
>NeoChat</productname>
</refentryinfo>
<refmeta>
<refentrytitle>
<command
>neochat</command>
</refentrytitle>
<manvolnum
>1</manvolnum>
</refmeta>
<refnamediv>
<refname
>neochat</refname>
<refpurpose
>Клиент для взаимодействия с протоколом обмена сообщениями Matrix</refpurpose>
</refnamediv>
<!-- body begins here -->
<refsynopsisdiv id='synopsis'>
<cmdsynopsis
><command
>neochat</command
> <arg choice="opt"
><replaceable
>URI</replaceable
></arg
> </cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
<title
>Описание</title>
<para
><command
>neochat</command
> — приложение для настольных и мобильных устройств, позволяющее общаться в чатах с помощью протокола Matrix. </para>
</refsect1>
<refsect1 id="options"
><title
>Параметры</title>
<variablelist>
<varlistentry>
<term
><option
>URI</option
></term>
<listitem>
<para
>URI-адрес пользователя или комнаты в Matrix, например: matrix:u/user:example.org и matrix:r/root:example.org. NeoChat попытается открыть указанную комнату или беседу. </para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="bug">
<title
>Отчёты об ошибках</title>
<para
>Сообщать об ошибках и отправлять предложения по улучшению можно по адресу <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General"
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General</ulink
></para>
</refsect1>
<refsect1>
<title
>Смотрите также</title>
<simplelist>
<member
>Список наиболее часто задаваемых вопросов о Matrix <ulink url="https://matrix.org/faq/"
>https://matrix.org/faq/</ulink
> </member>
<member
>kf5options(7)</member>
<member
>qt5options(7)</member>
</simplelist>
</refsect1>
<refsect1 id="copyright"
><title
>Авторские права</title>
<para
>Авторские права &copy; Tobias Fella, 20202022 </para>
<para
>Авторские права &copy; Carl Schwan, 20202022 </para>
<para
>Лицензия: стандартная общественная лицензия GNU версии 3 или любой более поздней версии &lt;<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
>&gt;</para>
</refsect1>
</refentry>

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

@@ -8,6 +8,8 @@ endif()
add_subdirectory(libneochat)
add_subdirectory(login)
add_subdirectory(rooms)
add_subdirectory(roominfo)
add_subdirectory(messagecontent)
add_subdirectory(timeline)
add_subdirectory(spaces)
add_subdirectory(chatbar)

View File

@@ -8,8 +8,6 @@ add_library(neochat STATIC
controller.h
roommanager.cpp
roommanager.h
models/userfiltermodel.cpp
models/userfiltermodel.h
models/userdirectorylistmodel.cpp
models/userdirectorylistmodel.h
notificationsmanager.cpp
@@ -22,8 +20,6 @@ add_library(neochat STATIC
windowcontroller.h
models/serverlistmodel.cpp
models/serverlistmodel.h
logger.cpp
logger.h
models/notificationsmodel.cpp
models/notificationsmodel.h
proxycontroller.cpp
@@ -49,6 +45,9 @@ if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction
)
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME GlobalMenu
)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
@@ -58,18 +57,13 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AccountMenu.qml
qml/CollapsedRoomDelegate.qml
qml/RoomPage.qml
qml/ExploreRoomsPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/ImageEditorPage.qml
qml/NeochatMaximizeComponent.qml
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/AttachmentPane.qml
qml/QuickFormatBar.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
@@ -79,26 +73,14 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/EmojiSas.qml
qml/VerificationCanceled.qml
qml/MessageSourceSheet.qml
qml/RoomSearchPage.qml
qml/RoomPinnedMessagesPage.qml
qml/LocationChooser.qml
qml/InvitationView.qml
qml/AvatarTabButton.qml
qml/OsmLocationPlugin.qml
qml/FullScreenMap.qml
qml/LocationsPage.qml
qml/LocationMapItem.qml
qml/RoomDrawer.qml
qml/RoomDrawerPage.qml
qml/DirectChatDrawerHeader.qml
qml/GroupChatDrawerHeader.qml
qml/RoomInformation.qml
qml/RoomMedia.qml
qml/ChooseRoomDialog.qml
qml/RemoveChildDialog.qml
qml/QrCodeMaximizeComponent.qml
qml/NotificationsView.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
qml/UserSearchPage.qml
qml/ManualUserDialog.qml
@@ -118,12 +100,15 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AvatarNotification.qml
qml/ReasonDialog.qml
qml/NewPollDialog.qml
qml/UserMenu.qml
DEPENDENCIES
QtCore
QtQuick
IMPORTS
org.kde.neochat.libneochat
org.kde.neochat.rooms
org.kde.neochat.roominfo
org.kde.neochat.messagecontent
org.kde.neochat.timeline
org.kde.neochat.spaces
org.kde.neochat.settings
@@ -139,7 +124,10 @@ if(NOT ANDROID AND NOT WIN32)
qml/EditMenu.qml
)
else()
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
qt_target_qml_sources(neochat QML_FILES
qml/ShareActionStub.qml
qml/GlobalMenuStub.qml
)
endif()
if(WIN32)
@@ -189,7 +177,7 @@ else()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PUBLIC
LibNeoChat
Timeline
@@ -214,6 +202,7 @@ target_link_libraries(neochat PUBLIC
QuotientQt6
Login
Rooms
MessageContent
Spaces
)
@@ -368,3 +357,10 @@ install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()
if (APPLE)
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.neochat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "NeoChat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${RELEASE_SERVICE_VERSION})
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${RELEASE_SERVICE_VERSION})
endif ()

View File

@@ -32,6 +32,7 @@
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "proxycontroller.h"
#include "roommanager.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
@@ -98,6 +99,7 @@ Controller::Controller(QObject *parent)
MessageModel::setHiddenFilter(hiddenEventFilter);
RoomListModel::setHiddenFilter(hiddenEventFilter);
RoomTreeModel::setHiddenFilter(hiddenEventFilter);
NeoChatRoom::setHiddenFilter(hiddenEventFilter);
MediaSizeHelper::setMaxSize(NeoChatConfig::mediaMaxWidth(), NeoChatConfig::mediaMaxHeight());
connect(NeoChatConfig::self(), &NeoChatConfig::MediaMaxWidthChanged, this, []() {
@@ -230,6 +232,7 @@ void Controller::initConnection(NeoChatConnection *connection)
m_notificationsManager.handleNotifications(connection);
});
connect(this, &Controller::globalUrlPreviewDefaultChanged, connection, &NeoChatConnection::globalUrlPreviewEnabledChanged);
connect(connection, &NeoChatConnection::roomAboutToBeLeft, &RoomManager::instance(), &RoomManager::roomLeft);
Q_EMIT connectionAdded(connection);
}
@@ -315,8 +318,7 @@ void Controller::listenForNotifications()
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
instance().m_notificationsManager.postPushNotification(data);
timer->stop();
NotificationsManager::postPushNotification(data);
});
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
@@ -404,4 +406,9 @@ void Controller::markImageShown(const QString &eventId)
m_shownImages.append(eventId);
}
void Controller::markImageHidden(const QString &eventId)
{
m_shownImages.removeAll(eventId);
}
#include "moc_controller.cpp"

View File

@@ -99,6 +99,7 @@ public:
Q_INVOKABLE bool isImageShown(const QString &eventId);
Q_INVOKABLE void markImageShown(const QString &eventId);
Q_INVOKABLE void markImageHidden(const QString &eventId);
private:
explicit Controller(QObject *parent = nullptr);

View File

@@ -1,223 +0,0 @@
// SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
// SPDX-FileCopyrightText: 2002 Holger Freyther <freyther@kde.org>
// SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
// SPDX-FileCopyrightText: 2023 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "logger.h"
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QLoggingCategory>
#include <QMutex>
#include <QStandardPaths>
using namespace Qt::StringLiterals;
static QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr;
static QtMessageHandler oldHandler = nullptr;
static bool e2eeDebugEnabled = false;
class FileDebugStream : public QIODevice
{
Q_OBJECT
public:
FileDebugStream()
: mType(QtCriticalMsg)
{
open(WriteOnly);
}
bool isSequential() const override
{
return true;
}
qint64 readData(char *, qint64) override
{
return 0;
}
qint64 readLineData(char *, qint64) override
{
return 0;
}
qint64 writeData(const char *data, qint64 len) override
{
if (!mFileName.isEmpty()) {
QFile outputFile(mFileName);
outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered);
outputFile.write(data, len);
outputFile.putChar('\n');
outputFile.close();
}
return len;
}
void setFileName(const QString &fileName)
{
mFileName = fileName;
}
void setType(QtMsgType type)
{
mType = type;
}
private:
QString mFileName;
QtMsgType mType;
};
class DebugPrivate
{
public:
DebugPrivate()
: origHandler(nullptr)
{
}
~DebugPrivate()
{
qInstallMessageHandler(origHandler);
file.close();
}
void log(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
QMutexLocker locker(&mutex);
QByteArray buf;
QTextStream str(&buf);
str << QDateTime::currentDateTime().toString(Qt::ISODate) << u" ["_s;
switch (type) {
case QtDebugMsg:
str << u"DEBUG"_s;
break;
case QtInfoMsg:
str << u"INFO "_s;
break;
case QtWarningMsg:
str << u"WARN "_s;
break;
case QtFatalMsg:
str << u"FATAL"_s;
break;
case QtCriticalMsg:
str << u"CRITICAL"_s;
break;
}
str << u"] "_s << context.category << u": "_s;
if (context.file && *context.file && context.line) {
str << context.file << u":"_s << context.line << u": "_s;
}
if (context.function && *context.function) {
str << context.function << u": "_s;
}
str << message << u"\n"_s;
str.flush();
file.write(buf.constData(), buf.size());
file.flush();
if (oldHandler && (!context.category || (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled))) {
oldHandler(type, context, message);
}
}
void setName(const QString &appName)
{
name = appName;
if (file.isOpen()) {
file.close();
}
const auto &filePath = u"%1%2%3"_s.arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), QDir::separator(), appName);
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator());
auto entryList = dir.entryList({appName + u".*"_s});
std::sort(entryList.begin(), entryList.end(), [](const auto &left, const auto &right) {
auto leftIndex = left.split(u"."_s).last().toInt();
auto rightIndex = right.split(u"."_s).last().toInt();
return leftIndex > rightIndex;
});
for (const auto &entry : entryList) {
bool ok = false;
const auto index = entry.split(u"."_s).last().toInt(&ok);
if (!ok) {
continue;
}
QFileInfo info(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + entry);
if (info.exists()) {
QFile file(info.absoluteFilePath());
if (index > 50) {
file.remove();
continue;
}
const auto &newName = u"%1.%2"_s.arg(filePath, QString::number(index + 1));
const auto success = file.copy(newName);
if (success) {
file.remove();
} else {
qFatal("Cannot rename log file '%s' to '%s': %s",
qUtf8Printable(file.fileName()),
qUtf8Printable(newName),
qUtf8Printable(file.errorString()));
}
}
}
QFileInfo finfo(filePath);
if (!finfo.absoluteDir().exists()) {
QDir().mkpath(finfo.absolutePath());
}
file.setFileName(filePath + u".0"_s);
file.open(QIODevice::WriteOnly | QIODevice::Unbuffered);
}
void setOrigHandler(QtMessageHandler origHandler_)
{
origHandler = origHandler_;
}
QMutex mutex;
QFile file;
QString name;
QtMessageHandler origHandler;
QByteArray loggingCategory;
};
Q_GLOBAL_STATIC(DebugPrivate, sInstance)
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
switch (type) {
case QtDebugMsg:
case QtInfoMsg:
case QtWarningMsg:
case QtCriticalMsg:
sInstance()->log(type, context, message);
break;
case QtFatalMsg:
sInstance()->log(QtInfoMsg, context, message);
}
}
void filter(QLoggingCategory *category)
{
if (qstrcmp(category->categoryName(), "quotient.e2ee") == 0) {
category->setEnabled(QtDebugMsg, true);
} else if (oldCategoryFilter) {
oldCategoryFilter(category);
}
}
void initLogging()
{
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtInfoMsg).isEnabled(QtDebugMsg);
oldCategoryFilter = QLoggingCategory::installFilter(filter);
oldHandler = qInstallMessageHandler(messageHandler);
sInstance->setOrigHandler(oldHandler);
sInstance->setName(u"neochat.log"_s);
}
#include "logger.moc"

View File

@@ -1,9 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
/**
* Initlalize logging to file and enables some additional categories, which will only be logged to the file
*/
void initLogging();

View File

@@ -49,7 +49,6 @@
#include "blurhashimageprovider.h"
#include "colorschemer.h"
#include "controller.h"
#include "logger.h"
#include "login.h"
#include "registration.h"
#include "roommanager.h"
@@ -138,6 +137,11 @@ int main(int argc, char *argv[])
font.setHintingPreference(QFont::PreferNoHinting);
app.setFont(font);
#endif
#ifdef Q_OS_MACOS
QApplication::setStyle(u"breeze"_s);
#endif
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
QGuiApplication::setOrganizationName("KDE"_L1);
@@ -177,8 +181,6 @@ int main(int argc, char *argv[])
KCrash::initialize();
#endif
initLogging();
Connection::setEncryptionDefault(true);
Connection::setDirectChatEncryptionDefault(true);
@@ -195,12 +197,15 @@ int main(int argc, char *argv[])
parser.addPositionalArgument(u"urls"_s, i18n("Supports matrix: url scheme"));
parser.addOption(QCommandLineOption("ignore-ssl-errors"_L1, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
QCommandLineOption replaceOption({QStringLiteral("replace")}, i18nc("command line description", "Replace an existing instance"));
parser.addOption(replaceOption);
QCommandLineOption testOption("test"_L1, i18n("Only used for autotests"));
testOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(testOption);
#ifdef HAVE_KUNIFIEDPUSH
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s, i18n("Internal usage only."));
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s);
dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
parser.addOption(dbusActivatedOption);
#endif
@@ -214,8 +219,14 @@ int main(int argc, char *argv[])
#ifdef HAVE_KUNIFIEDPUSH
if (parser.isSet(dbusActivatedOption)) {
// We want to be replaceable by the main client
KDBusService service(KDBusService::Replace);
#ifdef HAVE_KDBUSADDONS
// 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
// If we are built with KRunner and KUnifiedPush support, we need to do something special.
@@ -231,7 +242,7 @@ int main(int argc, char *argv[])
#endif
#ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique);
KDBusService service(KDBusService::Unique | (parser.isSet(replaceOption) ? KDBusService::Replace : KDBusService::StartupOption(0)));
#endif
const auto accountManager = std::make_unique<AccountManager>(parser.isSet("test"_L1));
@@ -239,13 +250,6 @@ int main(int argc, char *argv[])
LoginHelper::instance().setAccountManager(accountManager.get());
Registration::instance().setAccountManager(accountManager.get());
Q_IMPORT_QML_PLUGIN(org_kde_neochat_settingsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_roomsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_loginPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
qml_register_types_org_kde_neochat();
qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s);

View File

@@ -176,7 +176,7 @@ void ServerListModel::initialize()
true,
false,
});
beginResetModel();
endResetModel();
}
#include "moc_serverlistmodel.cpp"

View File

@@ -42,7 +42,6 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
DesktopEntry=org.kde.neochat
@@ -87,7 +86,6 @@ Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokolle
Comment[ta]=மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி
Comment[tr]=Merkezi olmayan iletişim protokolü Matrix için bir istemci
Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними
Comment[x-test]=xxA client for matrix, the decentralized communication protocolxx
Comment[zh_CN]=分布式通讯协议 Matrix 的客户端
Comment[zh_TW]=去中心化通訊協定 Matrix 的用戶端
@@ -134,7 +132,6 @@ Name[sv]=Nytt meddelande
Name[ta]=புதிய செய்தி
Name[tr]=Yeni İleti
Name[uk]=Нове повідомлення
Name[x-test]=xxNew messagexx
Name[zh_CN]=新消息
Name[zh_TW]=新訊息
Comment=There is a new message
@@ -177,7 +174,6 @@ Comment[sv]=Det finns ett nytt meddelande
Comment[ta]=ஒரு புதிய செய்தி உள்ளது
Comment[tr]=Yeni bir ileti var
Comment[uk]=Надійшло нове повідомлення
Comment[x-test]=xxThere is a new messagexx
Comment[zh_CN]=有新消息
Comment[zh_TW]=有新的訊息
Action=Popup
@@ -215,6 +211,7 @@ Name[pa]=ਨਵਾਂ ਸੱਦਾ
Name[pl]=Nowe zaproszenie
Name[pt]=Novo Convite
Name[pt_BR]=Novo convite
Name[ro]=Invitație nouă
Name[ru]=Новое приглашение
Name[sa]=नवीन आमन्त्रणम्
Name[sl]=Novo povabilo
@@ -222,7 +219,6 @@ Name[sv]=Ny inbjudan
Name[ta]=புதிய அழைப்பிதழ்
Name[tr]=Yeni Davet
Name[uk]=Нове запрошення
Name[x-test]=xxNew Invitationxx
Name[zh_CN]=新邀请
Name[zh_TW]=新邀請
Comment=There is a new invitation to a room
@@ -257,6 +253,7 @@ Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
Comment[pt]=Existe um novo convite para uma sala
Comment[pt_BR]=Existe um novo convite para uma sala
Comment[ro]=E o nouă invitație la o cameră
Comment[ru]=Доступно новое приглашение в комнату
Comment[sa]=कक्षस्य नूतनं निमन्त्रणम् अस्ति
Comment[sl]=Tam je novo povabilo v sobo
@@ -264,7 +261,6 @@ Comment[sv]=Det finns en ny inbjudan till ett rum
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
Comment[tr]=Bir odaya yeni bir davetiye var
Comment[uk]=У кімнаті нове запрошення
Comment[x-test]=xxThere is a new invitation to a roomxx
Comment[zh_CN]=有新的聊天室邀请
Comment[zh_TW]=有新的加入聊天室邀請
Action=Popup
@@ -296,6 +292,7 @@ Name[nl]=Gedeelde
Name[nn]=Del
Name[pl]=Udostępnij
Name[pt_BR]=Compartilhar
Name[ro]=Partajare
Name[ru]=Публикация
Name[sa]=संविभागः
Name[sl]=Deli
@@ -303,7 +300,6 @@ Name[sv]=Dela
Name[ta]=பகிர்
Name[tr]=Paylaş
Name[uk]=Оприлюднення
Name[x-test]=xxSharexx
Name[zh_CN]=分享
Name[zh_TW]=分享
Comment=The result of sharing a piece of content
@@ -331,6 +327,7 @@ Comment[nl]=Het resultaat van het delen van een stukje inhoud
Comment[nn]=Resultatet av deling av innhald
Comment[pl]=Wynik udostępniania kawałka treści
Comment[pt_BR]=O resultado de compartilhar um conteúdo
Comment[ro]=Rezultatul partajării unei bucăți de conținut
Comment[ru]=Результат публикации данных
Comment[sa]=सामग्रीखण्डस्य साझाकरणस्य परिणामः
Comment[sl]=Rezultat deljenega kosa vsebine
@@ -338,7 +335,6 @@ Comment[sv]=Resultatet av att dela innehåll
Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவு
Comment[tr]=Bir parça içerik paylaşımının sonucu
Comment[uk]=Результат оприлюднення даних
Comment[x-test]=xxThe result of sharing a piece of contentxx
Comment[zh_CN]=分享一个内容得到的结果
Comment[zh_TW]=分享一份內容之後的結果
Action=Popup

View File

@@ -78,6 +78,12 @@
<label>Use a compact room list layout</label>
<default>false</default>
</entry>
<entry name="MarkReadCondition" type="Enum">
<label>The sort order for the rooms in the list.</label>
<choices name="::TimelineMarkReadCondition::Condition">
</choices>
<default>2</default>
</entry>
<entry name="ShowStateEvent" type="bool">
<label>Show state events in the timeline</label>
<default>true</default>
@@ -205,6 +211,10 @@
<label>Enable add phone numbers as 3PIDs</label>
<default>false</default>
</entry>
<entry name="Calls" type="bool">
<label>Enable audio and video calling</label>
<default>false</default>
</entry>
</group>
<group name="Security">
<entry name="RejectUnknownInvites" type="bool">

View File

@@ -276,7 +276,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom)
if (inAnyOfOurRooms) {
doPostInviteNotification(room);
} else {
room->leaveRoom();
room->forget();
}
}
});
@@ -330,14 +330,14 @@ void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
if (!room) {
return;
}
RoomManager::instance().leaveRoom(room);
room->forget();
notification->close();
});
connect(rejectAndIgnoreAction, &KNotificationAction::activated, this, [room, notification]() {
if (!room) {
return;
}
RoomManager::instance().leaveRoom(room);
room->forget();
room->connection()->addToIgnoredUsers(room->invitingUserId());
notification->close();
});
@@ -389,7 +389,7 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
#ifdef HAVE_KIO
auto openAction = notification->addAction(i18n("Open NeoChat"));
connect(openAction, &KNotificationAction::activated, this, [=]() {
connect(openAction, &KNotificationAction::activated, notification, [=]() {
QString properId = roomId;
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);
notification->sendEvent();
m_notifications.insert(roomId, {json["ts"_L1].toVariant().toLongLong(), notification});
} else {
qWarning() << "Skipping unsupported push notification" << type;
}

View File

@@ -53,7 +53,7 @@ public:
/**
* @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.

View File

@@ -43,7 +43,6 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
Comment=Find rooms in NeoChat
@@ -76,6 +75,7 @@ Comment[nn]=Finn rom i NeoChat
Comment[pl]=Znajdź pokoje w NeoChat
Comment[pt]=Procurar salas no NeoChat
Comment[pt_BR]=Encontrar salas no NeoChat
Comment[ro]=Găsește camere în NeoChat
Comment[ru]=Поиск комнат NeoChat
Comment[sa]=NeoChat इत्यत्र कक्ष्याः अन्वेषणं कुर्वन्तु
Comment[sl]=Najdi sobe v NeoChatu
@@ -83,7 +83,6 @@ Comment[sv]=Sök efter rum i NeoChat
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
Comment[tr]=NeoChatte odalar bulun
Comment[uk]=Пошук кімнат у NeoChat
Comment[x-test]=xxFind rooms in NeoChatxx
Comment[zh_CN]=在 NeoChat 查找聊天室
Comment[zh_TW]=在 NeoChat 尋找聊天室
X-KDE-ServiceTypes=Plasma/Runner

View File

@@ -36,14 +36,18 @@ KirigamiComponents.ConvergentContextMenu {
}
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Switch Account")
icon.name: "system-switch-user"
shortcut: "Ctrl+U"
onTriggered: accountSwitchDialog.createObject(QQC2.Overlay.overlay, {
connection: root.connection
}).open();
}
QQC2.Action {
text: i18n("Edit This Account")
icon.name: "document-edit"
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), {
connection: root.connection
}, {
title: i18n("Account editor")
})
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection});
}
QQC2.Action {
@@ -92,7 +96,7 @@ KirigamiComponents.ConvergentContextMenu {
const dialog = Qt.createComponent("org.kde.kirigami", "PromptDialog").createObject(QQC2.Overlay.overlay, {
title: i18nc("@title", "Verification Request Sent"),
subtitle: i18nc("@info:label", "To proceed, accept the verification request on another device."),
standardButtons: QQC2.Dialog.Ok
standardButtons: Kirigami.Dialog.Ok
})
dialog.open();
root.connection.onNewKeyVerificationSession.connect(() => {

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
@@ -12,7 +11,7 @@ import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat
QQC2.Dialog {
Kirigami.Dialog {
id: root
required property NeoChatConnection connection
@@ -24,7 +23,7 @@ QQC2.Dialog {
topPadding: 0
bottomPadding: 0
standardButtons: QQC2.Dialog.NoButton
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title: dialog to switch between logged in accounts", "Switch Account")

View File

@@ -8,30 +8,33 @@ import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.PromptDialog {
Kirigami.Dialog {
id: root
required property var user
title: i18nc("@title:dialog", "Start a chat")
subtitle: i18n("Do you want to start a chat with %1?", root.user.displayName)
dialogType: Kirigami.PromptDialog.Warning
width: Math.min(Kirigami.Units.gridUnit * 24, QQC2.ApplicationWindow.window.width)
height: Kirigami.Units.gridUnit * 8
onRejected: {
root.close();
standardButtons: QQC2.Dialog.Close
title: i18nc("@title:dialog", "Start a chat")
contentItem: QQC2.Label {
text: i18n("Do you want to start a chat with %1?", root.user.displayName)
textFormat: Text.PlainText
wrapMode: Text.Wrap
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.Dialog.Cancel
QQC2.Button {
customFooterActions: [
Kirigami.Action {
text: i18nc("@action:button", "Start Chat")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
icon.name: "im-user"
onClicked: {
onTriggered: {
root.user.requestDirectChat();
root.close();
}
}
}
]
}

View File

@@ -48,6 +48,7 @@ Delegates.RoundedItemDelegate {
TapHandler {
acceptedDevices: PointerDevice.TouchScreen
onTapped: root.selected()
onLongPressed: root.contextMenuRequested()
}

View File

@@ -28,7 +28,7 @@ Kirigami.PromptDialog {
text: i18nc("@action:button", "Leave Room")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
icon.name: "arrow-left-symbolic"
onClicked: RoomManager.leaveRoom(root.room)
onClicked: root.room.forget();
}
}
}

View File

@@ -8,30 +8,32 @@ import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.PromptDialog {
Kirigami.Dialog {
id: root
required property string url
width: Math.min(Kirigami.Units.gridUnit * 24, QQC2.ApplicationWindow.window.width)
height: Kirigami.Units.gridUnit * 8
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
title: i18nc("@title:dialog", "User Consent")
subtitle: i18nc("@info", "Your homeserver requires you to agree to its terms and conditions before being able to use it. Please click the button below to read them.")
dialogType: Kirigami.PromptDialog.Warning
onRejected: {
root.close();
contentItem: QQC2.Label {
text: i18nc("@info", "Your homeserver requires you to agree to its terms and conditions before being able to use it. Please click the button below to read them.")
wrapMode: Text.WordWrap
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.Dialog.Cancel
QQC2.Button {
customFooterActions: [
Kirigami.Action {
text: i18nc("@action:button", "Open")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
icon.name: "internet-services"
onClicked: {
onTriggered: {
UrlHelper.openUrl(root.url);
root.close();
}
}
}
]
}

View File

@@ -8,6 +8,8 @@ import QtPositioning
import org.kde.kirigami as Kirigami
import org.kde.neochat.libneochat
ApplicationWindow {
id: root

View File

@@ -15,7 +15,7 @@ import org.kde.neochat.settings
Labs.MenuBar {
id: root
property NeoChatConnection connection
required property NeoChatConnection connection
Labs.Menu {
title: i18nc("menu", "NeoChat")
@@ -38,25 +38,31 @@ Labs.MenuBar {
title: i18nc("menu", "File")
Labs.MenuItem {
text: i18nc("menu", "Find your friends")
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: pushReplaceLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find your friends")
})
}
Labs.MenuItem {
text: i18nc("menu", "New Group…")
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: {
const dialog = createRoomDialog.createObject(root.overlay);
dialog.open();
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection
}, {
title: i18nc("@title", "Create a Room")
});
}
}
Labs.MenuItem {
text: i18nc("menu", "Browse Chats…")
icon.name: "compass-symbolic"
text: i18nc("@action:inmenu", "Explore Rooms")
onTriggered: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
@@ -77,7 +83,8 @@ Labs.MenuBar {
title: i18nc("menu", "View")
Labs.MenuItem {
text: i18nc("menu item that opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Open Quick Switcher")
icon.name: "search-symbolic"
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: quickSwitcher.open()
}
}
@@ -85,6 +92,7 @@ Labs.MenuBar {
title: i18nc("menu", "Window")
Labs.MenuItem {
icon.name: "view-fullscreen-symbolic"
text: root.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
onTriggered: root.visibility === Window.FullScreen ? root.showNormal() : root.showFullScreen()
}
@@ -93,14 +101,12 @@ Labs.MenuBar {
title: i18nc("menu", "Help")
Labs.MenuItem {
text: i18nc("menu", "About Matrix")
onTriggered: UrlHelper.openUrl("https://matrix.org/docs/chat_basics/matrix-for-im/")
}
Labs.MenuItem {
icon.name: "help-about-symbolic"
text: i18nc("menu", "About NeoChat")
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
}
Labs.MenuItem {
icon.name: "kde-symbolic"
text: i18nc("menu", "About KDE")
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage"))
}

View File

@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import org.kde.neochat
Item {
required property NeoChatConnection connection
}

View File

@@ -16,7 +16,7 @@ ColumnLayout {
id: root
required property NeoChatRoom currentRoom
readonly property var invitingMember: currentRoom.member(currentRoom.invitingUserId)
readonly property var invitingMember: currentRoom.qmlSafeMember(currentRoom.invitingUserId)
readonly property string inviteTimestamp: root.currentRoom.inviteTimestamp.toLocaleString(Qt.locale(), Locale.ShortFormat)
spacing: Kirigami.Units.smallSpacing
@@ -33,7 +33,7 @@ ColumnLayout {
Layout.fillWidth: true
name: root.invitingMember.displayName
source: root.invitingMember.avatarUrl
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
color: root.invitingMember.color
}
@@ -108,7 +108,7 @@ ColumnLayout {
icon.name: "dialog-cancel-symbolic"
text: i18nc("@action:button Reject this invite", "Reject Invite")
onClicked: RoomManager.leaveRoom(root.currentRoom)
onClicked: root.currentRoom.forget()
}
}
@@ -123,7 +123,7 @@ ColumnLayout {
text: i18nc("@action:button Block the user", "Block %1", root.invitingMember.displayName)
onClicked: {
RoomManager.leaveRoom(root.currentRoom);
root.currentRoom.forget()
root.currentRoom.connection.addToIgnoredUsers(root.currentRoom.invitingUserId);
}
}

View File

@@ -12,7 +12,7 @@ import org.kde.prison
import org.kde.neochat
QQC2.Dialog {
Kirigami.Dialog {
id: root
required property string room
@@ -23,7 +23,7 @@ QQC2.Dialog {
topPadding: 0
bottomPadding: 0
standardButtons: QQC2.Dialog.NoButton
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title:dialog", "Join Room")

View File

@@ -41,6 +41,7 @@ Kirigami.ApplicationWindow {
showExisting: true
onConnectionChosen: root.load()
}
columnView.columnResizeMode: pageStack.wideMode ? Kirigami.ColumnView.DynamicColumns : Kirigami.ColumnView.SingleColumn
globalToolBar.canContainHandles: true
globalToolBar {
style: Kirigami.ApplicationHeaderStyle.ToolBar
@@ -80,9 +81,8 @@ Kirigami.ApplicationWindow {
Loader {
active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile
sourceComponent: Qt.createComponent("org.kde.neochat", "GlobalMenu")
onActiveChanged: if (active) {
item.connection = root.connection;
sourceComponent: GlobalMenu {
connection: root.connection
}
}
@@ -149,9 +149,13 @@ Kirigami.ApplicationWindow {
}
function openRoomDrawer() {
pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomDrawerPage'), {
connection: root.connection
const page = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomDrawerPage'), {
connection: root.connection,
room: RoomManager.currentRoom,
userListModel: RoomManager.userListModel,
mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
});
page.resolveResource.connect((idOrUri, action) => RoomManager.resolveResource(idOrUri, action))
}
contextDrawer: RoomDrawer {
@@ -161,7 +165,18 @@ Kirigami.ApplicationWindow {
// It is used to ensure that user choice is remembered when changing pages and expanding and contracting the window width
property bool drawerUserState: NeoChatConfig.autoRoomInfoDrawer
room: RoomManager.currentRoom
connection: root.connection
userListModel: RoomManager.userListModel
mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
onResolveResource: (idOrUri, action) => RoomManager.resolveResource(idOrUri, action)
roomDrawerWidth: NeoChatConfig.roomDrawerWidth
onRoomDrawerWidthChanged: {
NeoChatConfig.roomDrawerWidth = actualWidth;
NeoChatConfig.save();
}
handleClosedIcon.source: "documentinfo-symbolic"
handleClosedToolTip: i18nc("@action:button", "Show Room Information")

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Window
import QtQuick.Layouts
@@ -11,7 +10,7 @@ import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
QQC2.Dialog {
Kirigami.Dialog {
id: root
/**
@@ -32,41 +31,35 @@ QQC2.Dialog {
topPadding: 0
bottomPadding: 0
footer: QQC2.DialogButtonBox {
QQC2.Button {
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
standardButtons: Kirigami.Dialog.Cancel
customFooterActions: [
Kirigami.Action {
enabled: roomIdAliasText.isValidText
text: i18n("OK")
icon.name: "dialog-ok"
onTriggered: {
// We don't necessarily have all the info so fill out the best we can.
let roomId = roomIdAliasText.isAlias() ? "" : roomIdAliasText.text;
let displayName = "";
let avatarUrl = "";
let alias = roomIdAliasText.isAlias() ? roomIdAliasText.text : "";
let topic = "";
let memberCount = -1;
let isJoined = false;
if (roomIdAliasText.room) {
roomId = roomIdAliasText.room.id;
displayName = roomIdAliasText.room.displayName;
avatarUrl = roomIdAliasText.room.avatarUrl.toString().length > 0 ? connection.makeMediaUrl(roomIdAliasText.room.avatarUrl) : "";
alias = roomIdAliasText.room.canonicalAlias;
topic = roomIdAliasText.room.topic;
memberCount = roomIdAliasText.room.joinedCount;
isJoined = true;
}
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
root.close();
}
}
QQC2.Button {
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
text: i18n("Cancel")
icon.name: "dialog-cancel"
}
}
onAccepted: {
// We don't necessarily have all the info so fill out the best we can.
let roomId = roomIdAliasText.isAlias() ? "" : roomIdAliasText.text;
let displayName = "";
let avatarUrl = "";
let alias = roomIdAliasText.isAlias() ? roomIdAliasText.text : "";
let topic = "";
let memberCount = -1;
let isJoined = false;
if (roomIdAliasText.room) {
roomId = roomIdAliasText.room.id;
displayName = roomIdAliasText.room.displayName;
avatarUrl = roomIdAliasText.room.avatarUrl.toString().length > 0 ? connection.makeMediaUrl(roomIdAliasText.room.avatarUrl) : "";
alias = roomIdAliasText.room.canonicalAlias;
topic = roomIdAliasText.room.topic;
memberCount = roomIdAliasText.room.joinedCount;
isJoined = true;
}
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
root.close();
}
]
contentItem: ColumnLayout {
spacing: 0

View File

@@ -10,7 +10,7 @@ import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
QQC2.Dialog {
Kirigami.Dialog {
id: root
/**
@@ -31,6 +31,19 @@ QQC2.Dialog {
topPadding: 0
bottomPadding: 0
standardButtons: Kirigami.Dialog.Cancel
customFooterActions: [
Kirigami.Action {
enabled: userIdText.isValidText
text: i18n("OK")
icon.name: "dialog-ok"
onTriggered: {
root.connection.requestDirectChat(userIdText.text);
root.accept();
}
}
]
contentItem: ColumnLayout {
spacing: 0
FormCard.FormTextFieldDelegate {
@@ -62,21 +75,6 @@ QQC2.Dialog {
}
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.Dialog.Cancel
QQC2.Button {
enabled: userIdText.isValidText
text: i18n("OK")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
icon.name: "dialog-ok"
onClicked: {
root.connection.requestDirectChat(userIdText.text);
root.accept();
}
}
}
onVisibleChanged: {
userIdText.forceActiveFocus();
timer.restart();

View File

@@ -139,7 +139,7 @@ Components.AlbumMaximizeComponent {
id: saveAsDialog
Dialogs.FileDialog {
fileMode: Dialogs.FileDialog.SaveFile
currentFolder: root.saveFolder
currentFolder: NeoChatConfig.lastSaveDirectory.length > 0 ? NeoChatConfig.lastSaveDirectory : Core.StandardPaths.writableLocation(Core.StandardPaths.DownloadLocation)
onAccepted: {
NeoChatConfig.lastSaveDirectory = currentFolder;
NeoChatConfig.save();

View File

@@ -15,18 +15,27 @@ import Quotient
import org.kde.neochat
QQC2.Dialog {
Kirigami.Dialog {
id: root
required property NeoChatRoom room
title: i18nc("@title: create new poll in the room", "Create Poll")
standardButtons: Kirigami.Dialog.Cancel
customFooterActions: [
Kirigami.Action {
enabled: optionModel.allValuesSet && questionTextField.text.length > 0
text: i18nc("@action:button", "Send")
icon.name: "document-send"
onTriggered: {
root.room.postPoll(pollTypeCombo.currentValue, questionTextField.text, optionModel.values())
root.close()
}
}
]
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
title: i18nc("@title: create new poll in the room", "Create Poll")
contentItem: ColumnLayout {
spacing: 0
@@ -144,19 +153,4 @@ QQC2.Dialog {
onClicked: optionModel.append({optionText: ""})
}
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.Dialog.Cancel
QQC2.Button {
enabled: optionModel.allValuesSet && questionTextField.text.length > 0
text: i18nc("@action:button", "Send")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
icon.name: "document-send"
onClicked: {
root.room.postPoll(pollTypeCombo.currentValue, questionTextField.text, optionModel.values())
root.close()
}
}
}
}

View File

@@ -28,6 +28,14 @@ Kirigami.Page {
placeholderText: root.placeholder
anchors.fill: parent
wrapMode: TextEdit.Wrap
focus: true
Keys.onReturnPressed: event => {
if (event.modifiers & Qt.ControlModifier) {
root.accepted(reason.text);
root.closeDialog();
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
@@ -50,6 +58,7 @@ Kirigami.Page {
}
}
QQC2.Button {
icon.name: "dialog-cancel-symbolic"
text: i18nc("@action", "Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: root.closeDialog()

View File

@@ -10,11 +10,13 @@ import org.kde.kirigamiaddons.components
import org.kde.neochat
QQC2.Dialog {
Kirigami.Dialog {
id: root
property var connection
parent: applicationWindow().overlay
leftPadding: 0
rightPadding: 0
topPadding: 0

View File

@@ -11,15 +11,14 @@ import org.kde.kirigami as Kirigami
import org.kde.kitemmodels
import org.kde.neochat
import org.kde.neochat.chatbar
Kirigami.Page {
id: root
/// Not readonly because of the separate window view.
property NeoChatRoom currentRoom: RoomManager.currentRoom
required property NeoChatConnection connection
/**
* @brief The NeoChatRoom the delegate is being displayed in.
*/
readonly property NeoChatRoom currentRoom: RoomManager.currentRoom
/**
* @brief The TimelineModel to use.
@@ -59,11 +58,6 @@ Kirigami.Page {
*/
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
property bool loading: !root.currentRoom || (root.currentRoom.timelineSize === 0 && !root.currentRoom.allHistoryLoaded)
/// Disable cancel shortcut. Used by the separate window since it provides its own cancel implementation.
property bool disableCancelShortcut: false
title: root.currentRoom ? root.currentRoom.displayName : ""
focus: true
padding: 0
@@ -86,9 +80,9 @@ Kirigami.Page {
}
Connections {
target: root.connection
target: root.currentRoom.connection
function onIsOnlineChanged() {
if (!root.connection.isOnline) {
if (!root.currentRoom.connection.isOnline) {
banner.text = i18n("NeoChat is offline. Please check your network connection.");
banner.visible = true;
banner.type = Kirigami.MessageType.Error;
@@ -109,18 +103,15 @@ Kirigami.Page {
Loader {
id: timelineViewLoader
anchors.fill: parent
active: root.currentRoom && !root.currentRoom.isInvite && !root.loading && !root.currentRoom.isSpace
active: root.currentRoom && !root.currentRoom.isInvite && !root.currentRoom.isSpace
// We need the loader to be active but invisible while the room is loading messages so signals in TimelineView work.
visible: !root.loading
sourceComponent: TimelineView {
id: timelineView
currentRoom: root.currentRoom
page: root
timelineModel: root.timelineModel
messageFilterModel: root.messageFilterModel
onFocusChatBar: {
if (chatBarLoader.item) {
chatBarLoader.item.forceActiveFocus();
}
}
compactLayout: NeoChatConfig.compactLayout
fileDropEnabled: !Controller.isFlatpak
markReadCondition: NeoChatConfig.markReadCondition
}
}
@@ -137,7 +128,9 @@ Kirigami.Page {
id: spaceLoader
active: root.currentRoom && root.currentRoom.isSpace
anchors.fill: parent
sourceComponent: SpaceHomePage {}
sourceComponent: SpaceHomePage {
room: root.currentRoom
}
}
Loader {
@@ -150,14 +143,6 @@ Kirigami.Page {
}
}
Loader {
active: root.loading && !invitationLoader.active && RoomManager.currentRoom && !spaceLoader.active
anchors.centerIn: parent
sourceComponent: Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
}
}
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
@@ -172,12 +157,7 @@ Kirigami.Page {
id: chatBar
width: parent.width
currentRoom: root.currentRoom
connection: root.connection
onMessageSent: {
if (!timelineViewLoader.item.atYEnd) {
timelineViewLoader.item.goToLastMessage();
}
}
connection: root.currentRoom.connection
}
}
@@ -194,21 +174,8 @@ Kirigami.Page {
}
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: {
if (!timelineViewLoader.item.atYEnd || !root.currentRoom.partiallyReadStats.empty()) {
timelineViewLoader.item.goToLastMessage();
root.currentRoom.markAllMessagesAsRead();
} else {
applicationWindow().pageStack.get(0).forceActiveFocus();
}
}
enabled: !root.disableCancelShortcut
}
Connections {
target: root.connection
target: root.currentRoom.connection
function onJoinedRoom(room, invited) {
if (root.currentRoom.id === invited.id) {
RoomManager.resolveResource(room.id);
@@ -264,6 +231,7 @@ Kirigami.Page {
plainText: plainText,
mimeType: mimeType,
progressInfo: progressInfo,
messageComponentType: messageComponentType,
});
contextMenu.popup();
}
@@ -293,7 +261,7 @@ Kirigami.Page {
id: messageDelegateContextMenu
MessageDelegateContextMenu {
room: root.currentRoom
connection: root.connection
connection: root.currentRoom.connection
}
}
@@ -301,7 +269,7 @@ Kirigami.Page {
id: fileDelegateContextMenu
FileDelegateContextMenu {
room: root.currentRoom
connection: root.connection
connection: root.currentRoom.connection
}
}

View File

@@ -102,7 +102,7 @@ QQC2.ComboBox {
}
}
QQC2.Dialog {
Kirigami.Dialog {
id: addServerSheet
width: Math.min(Kirigami.Units.gridUnit * 15, QQC2.ApplicationWindow.window.width)
@@ -182,19 +182,15 @@ QQC2.ComboBox {
}
}
footer: QQC2.DialogButtonBox {
QQC2.Button {
enabled: serverUrlField.acceptableInput && serverUrlField.isValidServer
text: i18nc("@action:button", "Ok")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
icon.name: "dialog-ok"
onClicked: {
serverListModel.addServer(serverUrlField.text);
root.currentIndex = root.indexOfValue(serverUrlField.text);
root.server = root.currentValue;
serverUrlField.text = "";
addServerSheet.close();
}
customFooterActions: Kirigami.Action {
text: i18nc("@action:button", "Ok")
enabled: serverUrlField.acceptableInput && serverUrlField.isValidServer
onTriggered: {
serverListModel.addServer(serverUrlField.text);
root.currentIndex = root.indexOfValue(serverUrlField.text);
root.server = root.currentValue;
serverUrlField.text = "";
addServerSheet.close();
}
}
}

View File

@@ -13,7 +13,7 @@ import org.kde.prison
import org.kde.neochat
QQC2.Dialog {
Kirigami.Dialog {
id: root
// This dialog is sometimes used outside the context of a room, e.g., when scanning a user's QR code.
@@ -28,7 +28,7 @@ QQC2.Dialog {
topPadding: 0
bottomPadding: 0
standardButtons: QQC2.Dialog.NoButton
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title:menu Account details dialog", "Account Details")
@@ -80,7 +80,6 @@ QQC2.Dialog {
text: root.user.id
elide: Qt.ElideRight
elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
onElideWidthChanged: console.warn(root.availableWidth, avatar.width, qrButton.width, elideWidth, elidedText)
}
}

74
src/app/qml/UserMenu.qml Normal file
View File

@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.settings
import org.kde.neochat.devtools
KirigamiComponents.ConvergentContextMenu {
id: root
required property Kirigami.ApplicationWindow window
required property var author
headerContentItem: RowLayout {
id: detailRow
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
name: root.author.displayName
source: root.author.avatarUrl
color: root.author.color
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Kirigami.Heading {
level: 1
Layout.fillWidth: true
font.bold: true
elide: Text.ElideRight
wrapMode: Text.NoWrap
text: root.author.displayName
textFormat: Text.PlainText
}
QQC2.Label {
id: idLabel
textFormat: TextEdit.PlainText
text: root.author.id
elide: Qt.ElideRight
}
}
}
QQC2.Action {
text: i18nc("@action:button", "Open Profile")
icon.name: "im-user-symbolic"
onTriggered: RoomManager.resolveResource(root.author.uri)
}
QQC2.Action {
text: i18nc("@action:button", "Mention")
icon.name: "username-copy-symbolic"
onTriggered: {
RoomManager.currentRoom.mainCache.mentionAdded(root.author.id);
}
}
}

View File

@@ -11,7 +11,19 @@ VerificationMessage {
required property int reason
icon: "security-low"
icon: {
switch (root.reason) {
case KeyVerificationSession.TIMEOUT:
case KeyVerificationSession.REMOTE_TIMEOUT:
case KeyVerificationSession.USER:
case KeyVerificationSession.REMOTE_USER:
case KeyVerificationSession.SESSION_ACCEPTED:
case KeyVerificationSession.REMOTE_SESSION_ACCEPTED:
return "dialog-information";
default:
return "security-low";
}
}
text: {
switch (root.reason) {
case KeyVerificationSession.NONE:

View File

@@ -59,9 +59,9 @@ RoomManager::RoomManager(QObject *parent)
m_directChatsConfig = m_config->group(u"DirectChatsActive"_s);
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
m_userListModel->setRoom(m_currentRoom);
m_timelineModel->setRoom(m_currentRoom);
m_sortFilterRoomTreeModel->setCurrentRoom(m_currentRoom);
m_userListModel->setRoom(m_currentRoom);
});
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this](NeoChatConnection *connection) {
@@ -96,6 +96,7 @@ RoomManager::RoomManager(QObject *parent)
m_messageFilterModel->invalidate();
}
});
connect(m_timelineModel->timelineMessageModel(), &MessageModel::modelResetComplete, this, &RoomManager::activateUserModel);
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());
connect(NeoChatConfig::self(), &NeoChatConfig::ShowAllEventsChanged, this, [this] {
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());
@@ -303,7 +304,12 @@ void RoomManager::loadInitialRoom()
}
if (m_isMobile) {
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), false);
QString lastSpace = m_lastSpaceConfig.readEntry(m_connection->userId(), QString());
// We can't have empty keys in KConfig, so we stored it as "Home"
if (lastSpace == u"Home"_s) {
lastSpace.clear();
}
setCurrentSpace(lastSpace, false);
// We don't want to open a room on startup on mobile
return;
}
@@ -325,14 +331,7 @@ void RoomManager::openRoomForActiveConnection()
setCurrentSpace({}, false);
return;
}
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), false);
const auto &lastRoom = m_lastRoomConfig.readEntry(m_connection->userId(), QString());
if (lastRoom.isEmpty() || !m_connection->room(lastRoom)) {
setCurrentRoom({});
} else {
m_currentRoom = nullptr;
resolveResource(lastRoom);
}
setCurrentSpace(m_lastSpaceConfig.readEntry(m_connection->userId(), QString()), true);
}
UriResolveResult RoomManager::visitUser(User *user, const QString &action)
@@ -394,7 +393,9 @@ void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAli
// If no one gives us a homeserver suggestion, try the server specified in the alias/id.
// Otherwise joining a remote room not on our homeserver will fail.
if (vias.empty()) {
// This is a hack and we're not supposed to do it. With room ids not containing the server going forward, it won't work anymore for new room versions.
// FIXME: Let's keep it around anyway for now, remove it at some point, though
if (vias.empty() && roomAliasOrId.contains(':'_L1)) {
vias.append(roomAliasOrId.mid(roomAliasOrId.lastIndexOf(':'_L1) + 1));
}
@@ -448,29 +449,27 @@ void RoomManager::knockRoom(NeoChatConnection *account, const QString &roomAlias
Qt::SingleShotConnection);
}
void RoomManager::roomLeft(const QString &id)
{
if (id.isEmpty()) {
return;
}
if (m_currentRoom && m_currentRoom->id() == id) {
setCurrentRoom({});
}
if (m_currentSpaceId == id) {
setCurrentSpace({});
}
}
bool RoomManager::visitNonMatrix(const QUrl &url)
{
UrlHelper().openUrl(url);
return true;
}
void RoomManager::leaveRoom(NeoChatRoom *room)
{
if (!room) {
return;
}
if (m_currentRoom && m_currentRoom->id() == room->id()) {
setCurrentRoom({});
}
if (m_currentSpaceId == room->id()) {
setCurrentSpace({});
}
room->forget();
}
ChatDocumentHandler *RoomManager::chatDocumentHandler() const
{
return m_chatDocumentHandler;
@@ -523,18 +522,32 @@ void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom)
Q_EMIT currentSpaceChanged();
if (m_connection) {
m_lastSpaceConfig.writeEntry(m_connection->userId(), spaceId);
if (spaceId.isEmpty()) {
m_lastSpaceConfig.writeEntry(m_connection->userId(), u"Home"_s);
} else {
m_lastSpaceConfig.writeEntry(m_connection->userId(), spaceId);
}
}
if (!setRoom) {
return;
}
// We intentionally don't want to open the last room on mobile
if (!m_isMobile) {
if (spaceId.length() > 3) {
resolveResource(spaceId, "no_join"_L1);
QString configSpaceId = spaceId;
// We can't have empty keys in KConfig, so it's stored as "Home"
if (spaceId.isEmpty()) {
configSpaceId = u"Home"_s;
}
const auto &lastRoom = m_lastRoomConfig.readEntry(configSpaceId, QString());
if (lastRoom.isEmpty()) {
if (spaceId != u"DM"_s && spaceId != u"Home"_s) {
resolveResource(spaceId, "no_join"_L1);
}
} else {
visitRoom({}, {});
resolveResource(lastRoom, "no_join"_L1);
}
}
}
@@ -557,7 +570,16 @@ void RoomManager::setCurrentRoom(const QString &roomId)
Q_EMIT currentRoomChanged();
if (m_connection) {
m_lastRoomConfig.writeEntry(m_connection->userId(), roomId);
if (roomId.isEmpty()) {
m_lastRoomConfig.deleteEntry(m_currentSpaceId);
} else {
// We can't have empty keys in KConfig, so name it "Home"
if (m_currentSpaceId.isEmpty()) {
m_lastRoomConfig.writeEntry(u"Home"_s, roomId);
} else {
m_lastRoomConfig.writeEntry(m_currentSpaceId, roomId);
}
}
}
if (roomId.isEmpty()) {
return;

View File

@@ -195,11 +195,6 @@ public:
*/
Q_INVOKABLE void loadInitialRoom();
/**
* @brief Leave the room and close it if it is open.
*/
Q_INVOKABLE void leaveRoom(NeoChatRoom *room);
/**
* @brief Knock a room.
*
@@ -208,6 +203,13 @@ public:
*/
void knockRoom(NeoChatConnection *account, const QString &roomAliasOrId, const QString &reason, const QStringList &viaServers);
/**
* @brief Cleanup after the given room is left.
*
* This ensures that the current room and space are not set to the left room.
*/
void roomLeft(const QString &id);
/**
* @brief Show a media item maximized.
*

View File

@@ -15,4 +15,5 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
EmojiPicker.qml
EmojiDialog.qml
EmojiTonesPicker.qml
ImageEditorPage.qml
)

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