Compare commits

..

125 Commits

Author SHA1 Message Date
l10n daemon script
6e75739cda GIT_SILENT Sync po/docbooks with svn 2024-07-21 03:03:16 +00:00
l10n daemon script
8935e339c3 GIT_SILENT Sync po/docbooks with svn 2024-07-19 03:01:01 +00:00
l10n daemon script
1967247333 GIT_SILENT made messages (after extraction) 2024-07-19 02:28:16 +00:00
l10n daemon script
c68ce91b87 GIT_SILENT Sync po/docbooks with svn 2024-07-18 02:56:56 +00:00
l10n daemon script
1890dfbdac 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"
2024-07-16 02:53:50 +00:00
l10n daemon script
1d23264b1f GIT_SILENT Sync po/docbooks with svn 2024-07-15 03:03:33 +00:00
l10n daemon script
2915339b41 GIT_SILENT Sync po/docbooks with svn 2024-07-14 02:58:06 +00:00
l10n daemon script
767cda8c77 GIT_SILENT Sync po/docbooks with svn 2024-07-08 03:04:26 +00:00
l10n daemon script
a0fb6e2e42 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"
2024-07-07 03:00:56 +00:00
l10n daemon script
23b5fb554a GIT_SILENT Sync po/docbooks with svn 2024-07-02 03:01:51 +00:00
Heiko Becker
c00debf1c3 GIT_SILENT Update Appstream for new release 2024-06-28 23:17:51 +02:00
Heiko Becker
8446bd22f0 GIT_SILENT Upgrade release service version to 24.05.2. 2024-06-28 22:15:57 +02:00
l10n daemon script
f1e7fa74c2 GIT_SILENT Sync po/docbooks with svn 2024-06-28 03:23:58 +00:00
l10n daemon script
913f8ae91a 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"
2024-06-28 03:14:58 +00:00
l10n daemon script
5b250c73ec GIT_SILENT Sync po/docbooks with svn 2024-06-21 02:59:54 +00:00
Tobias Fella
4991c5f771 GlobalMenu: remove shortcut for QuickSwitcher
The shortcut needs to work when there is no Global Menu, so it's also in QuickSwitcher.qml.
It can't be in both places, since that breaks it. So we remove it here.

BUG: 488212
(cherry picked from commit db1bf61805)
2024-06-20 18:16:06 +02:00
Tobias Fella
95cb745536 Fix global menu
(cherry picked from commit 5456b4a7ff)
2024-06-20 18:04:44 +02:00
l10n daemon script
d09f597886 GIT_SILENT Sync po/docbooks with svn 2024-06-19 03:03:08 +00:00
Albert Astals Cid
1efebfb6a4 CI: Disable requiring Windows tests passing
Has been broken for 4 consecutive weeks

(cherry picked from commit 2daf3b5c4b)
2024-06-18 23:39:10 +02:00
l10n daemon script
7c65d2653a GIT_SILENT Sync po/docbooks with svn 2024-06-17 03:07:08 +00:00
l10n daemon script
8be3921ba6 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"
2024-06-17 03:03:29 +00:00
l10n daemon script
7b3fa17020 GIT_SILENT made messages (after extraction) 2024-06-17 02:33:53 +00:00
l10n daemon script
8e2d0523c3 GIT_SILENT Sync po/docbooks with svn 2024-06-14 03:00:16 +00:00
l10n daemon script
098966f2f6 GIT_SILENT made messages (after extraction) 2024-06-13 02:26:38 +00:00
l10n daemon script
e0496e73d9 GIT_SILENT Sync po/docbooks with svn 2024-06-12 03:08:06 +00:00
l10n daemon script
a58236647b GIT_SILENT Sync po/docbooks with svn 2024-06-11 03:07:10 +00:00
l10n daemon script
03db319a24 GIT_SILENT Sync po/docbooks with svn 2024-06-10 03:35:00 +00:00
l10n daemon script
cd9ce10fb0 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"
2024-06-10 03:21:57 +00:00
Heiko Becker
c5c47d7b67 GIT_SILENT Update Appstream for new release 2024-06-10 00:48:13 +02:00
Heiko Becker
2af04d5b00 GIT_SILENT Upgrade release service version to 24.05.1. 2024-06-09 23:41:21 +02:00
l10n daemon script
45743bc9bc GIT_SILENT Sync po/docbooks with svn 2024-06-09 03:06:39 +00:00
l10n daemon script
af83db6503 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"
2024-06-09 02:59:12 +00:00
l10n daemon script
06b2861e21 GIT_SILENT made messages (after extraction) 2024-06-09 02:28:25 +00:00
Joshua Goins
e54fef7476 Fix keyboard navigation on search pages
Some of our search pages (such as the room and user search) has a list
header item. Due to how this works, it's not actually a part of the
list view keyboard navigation and a whole separate item. So in the tab
order, it comes *after* the list view which makes no sense. And it's
part of the list view, so users must expect it to be selectable with the
up and down arrows like other items.

This simple change makes it so it behaves as expected. The first actual
list item is selected by default, but it's possible to navigate to the
list header item via the up arrow key and then return to the list view
using the down arrow. The list header item is also removed from the tab
order and the whole page is much nicer to use now.

(cherry picked from commit 364eda6400)
2024-06-08 11:48:12 -04:00
Joshua Goins
a96e3d00fc Fix the tooltips for the two drawer buttons at the top
One of them didn't even have a tooltip, which is a simple oversight
since it already has accessible text. The tooltips now use the attached
property instead of creating a new QQC2.ToolTip too.

(cherry picked from commit 7daae6a2d9)
2024-06-08 11:42:55 -04:00
Joshua Goins
67f5c0b1ed Fix keyboard navigation in space drawer
Some of the items were able to activated via the keyboard, but many were
not like the notifications and "create a space" buttons. This is because
the signals were hooked up to onClicked but the accessible and keyboard
nav were hooked up to onSelected. All of the buttons trigger their
actions with onSelected now.

(cherry picked from commit 277a4ad124)
2024-06-08 11:42:55 -04:00
Joshua Goins
b097f1c0ec Add keyboard navigation for server selection in room search dialog
This was previously not keyboard navigable at all, making it
impossible to switch servers in this dialog solely with a keyboard. This
patch makes it possible to do some basic selection but not deletion yet,
but it's a good start.

(cherry picked from commit b11d46e34a)
2024-06-08 11:42:55 -04:00
Joshua Goins
1467e43956 Remove room member highlight on click
Previously it was possible to keep clicking and highlighting each member
which doesn't make any sense. We could make this exclusive by having it
highlight only when index == currentIndex, but honestly it doesn't need
to be highlighted at all. Clicking on a room member opens their user
card, there's no persistent state the user needs to keep track of here.

(cherry picked from commit e8ad0a055d)
2024-06-08 11:42:55 -04:00
Joshua Goins
c8c26a0b23 Use Qt.alpha in ThemeRadioButton
This was newly added in Qt6 and simplifies a Qt.rgba call we used here.

(cherry picked from commit 8a8c745d77)
2024-06-08 10:36:21 -04:00
Joshua Goins
f344a8d690 Add focus border for the theme radio button, used on the Appearance page
Otherwise it's impossible to tell which option you're on, if you're
solely using a keyboard.

(cherry picked from commit a523fe7674)
2024-06-08 10:36:21 -04:00
Joshua Goins
696ce3af5e Fix QR code not showing when tapping the button under account settings
(cherry picked from commit dc9a150929)
2024-06-08 10:36:21 -04:00
Joshua Goins
5c220f3c53 Fix map copyright link activation
The argument was missing, so it wasn't possible to actually click and
visit the copyright notices linked on maps.

(cherry picked from commit be66ffef0f)
2024-06-08 10:36:21 -04:00
Joshua Goins
e7fe65bf57 Don't show the map if there's no locations available
It's hard to the read the text when there's a beige map behind it, and
unnecessary anyway.

(cherry picked from commit f278cc0c86)
2024-06-08 10:36:21 -04:00
Nicolas Fella
321bc293f3 Fixup AttachDialog
Use standard spacing values

Use implicit button size

(cherry picked from commit 7f72808a9a)
2024-06-08 10:36:21 -04:00
l10n daemon script
bd984f84ea GIT_SILENT Sync po/docbooks with svn 2024-06-08 03:06:03 +00:00
l10n daemon script
f36bc88745 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"
2024-06-08 03:02:10 +00:00
l10n daemon script
5f9402c1be GIT_SILENT made messages (after extraction) 2024-06-08 02:32:35 +00:00
l10n daemon script
155ca582f8 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"
2024-06-06 03:00:51 +00:00
l10n daemon script
d9eadca3a2 GIT_SILENT Sync po/docbooks with svn 2024-06-05 03:41:45 +00:00
l10n daemon script
da186fd57f GIT_SILENT Sync po/docbooks with svn 2024-06-03 02:57:59 +00:00
l10n daemon script
d04946b471 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"
2024-06-03 02:52:52 +00:00
l10n daemon script
cc823eff64 GIT_SILENT Sync po/docbooks with svn 2024-06-02 02:58:05 +00:00
l10n daemon script
6f36aa3929 GIT_SILENT made messages (after extraction) 2024-06-02 02:24:35 +00:00
l10n daemon script
196344ad49 GIT_SILENT Sync po/docbooks with svn 2024-06-01 02:59:44 +00:00
l10n daemon script
fbe5f1ff6d 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"
2024-06-01 02:56:30 +00:00
l10n daemon script
09c016fa8a GIT_SILENT made messages (after extraction) 2024-06-01 02:25:56 +00:00
Carl Schwan
e1448a5478 CreateRoomDialog: Add missing formcard separators
(cherry picked from commit 87d707bc21)

Co-authored-by: Carl Schwan <carl@carlschwan.eu>
2024-05-31 10:39:08 +00:00
l10n daemon script
8c8140533b GIT_SILENT Sync po/docbooks with svn 2024-05-31 03:23:45 +00:00
l10n daemon script
409579b287 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"
2024-05-31 03:16:55 +00:00
l10n daemon script
a6a4008944 GIT_SILENT Sync po/docbooks with svn 2024-05-29 03:49:31 +00:00
l10n daemon script
6bc3d55144 GIT_SILENT Sync po/docbooks with svn 2024-05-27 03:03:31 +00:00
l10n daemon script
32400358f6 GIT_SILENT Sync po/docbooks with svn 2024-05-21 03:08:53 +00:00
Tobias Fella
13846d485b Fix if X11 on apple
(cherry picked from commit 2a1e66468d)
2024-05-20 13:20:38 +00:00
Carl Schwan
c8d53b9e79 Cleanup quick switcher
- Make it modal
- Fix spacing
- Use bottom separator instead of frame for search field


(cherry picked from commit fe6ff9b2b6)
2024-05-20 13:19:59 +00:00
Tobias Fella
f8ff0dd3ce Fix spacing of HiddenDelegate
(cherry picked from commit 85ff8cdd4a)
2024-05-20 13:18:36 +00:00
Carl Schwan
e577af65d3 Fix micro spacing inconsistency in SpaceHierarchyDelegate
Exposing index allows RoundedItemDelegate to use a consistent padding
for the first and last item in the listview.


(cherry picked from commit 18c9376992)
2024-05-20 13:18:10 +00:00
l10n daemon script
6057e9a34f GIT_SILENT Sync po/docbooks with svn 2024-05-20 03:03:25 +00:00
l10n daemon script
99fdbc1882 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"
2024-05-19 02:59:13 +00:00
l10n daemon script
925d522da7 GIT_SILENT Sync po/docbooks with svn 2024-05-18 03:09:43 +00:00
l10n daemon script
283aa1dc72 GIT_SILENT Sync po/docbooks with svn 2024-05-17 03:02:13 +00:00
Heiko Becker
c66c035dbb GIT_SILENT Update Appstream for new release 2024-05-17 00:50:16 +02:00
Heiko Becker
cce9b967b9 GIT_SILENT Upgrade release service version to 24.05.0. 2024-05-16 23:52:26 +02:00
l10n daemon script
7b751a4e6e 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"
2024-05-14 02:57:25 +00:00
l10n daemon script
3b0cc5f4fc GIT_SILENT Sync po/docbooks with svn 2024-05-13 03:06:11 +00:00
l10n daemon script
5a15c013ff GIT_SILENT made messages (after extraction) 2024-05-10 02:29:41 +00:00
l10n daemon script
2351d76466 GIT_SILENT Sync po/docbooks with svn 2024-05-09 03:15:20 +00:00
l10n daemon script
f477ae0d5e 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"
2024-05-09 03:09:38 +00:00
Albert Astals Cid
f8d7a969ac GIT_SILENT Upgrade release service version to 24.04.90. 2024-05-09 01:20:59 +02:00
l10n daemon script
c4f79ea73a GIT_SILENT Sync po/docbooks with svn 2024-05-08 03:14:39 +00:00
l10n daemon script
9c132f2a7a GIT_SILENT made messages (after extraction) 2024-05-07 02:27:12 +00:00
l10n daemon script
ba2698585f GIT_SILENT Sync po/docbooks with svn 2024-05-06 03:21:02 +00:00
l10n daemon script
c31b716846 GIT_SILENT Sync po/docbooks with svn 2024-05-04 03:27:32 +00:00
Tobias Fella
49df3b2b2f Fix module
(cherry picked from commit 3a4aca7fbd)
2024-05-03 22:41:18 +02:00
Tobias Fella
8ef41b9c90 Adapt to behavior change in libQuotient
(cherry picked from commit 4e6850a60c)
2024-05-02 17:24:22 +02:00
l10n daemon script
2398d917f0 GIT_SILENT Sync po/docbooks with svn 2024-05-01 02:58:28 +00:00
Tobias Fella
3aefdb4aed Push ImageEditorPage using pushDialogLayer
BUG: 486315
(cherry picked from commit fdca7d58e5)
2024-04-30 23:36:55 +02:00
Tobias Fella
9a2c3e1deb Fix opening room on mobile 2024-04-30 22:05:55 +02:00
l10n daemon script
110d90bb51 GIT_SILENT Sync po/docbooks with svn 2024-04-30 02:59:12 +00:00
l10n daemon script
0a0cde77e6 GIT_SILENT Sync po/docbooks with svn 2024-04-29 02:58:36 +00:00
l10n daemon script
18d92ec475 GIT_SILENT Sync po/docbooks with svn 2024-04-28 02:55:43 +00:00
l10n daemon script
fd69439927 GIT_SILENT Sync po/docbooks with svn 2024-04-27 02:52:21 +00:00
Tobias Fella
ad97af20b7 Fix AddServerSheet
(cherry picked from commit 7e4361bb5e)
2024-04-26 19:11:24 +02:00
Tobias Fella
ca85b99fe9 Revert "Preserve mx-reply in the edited message if it exists"
This seems to cause bugs

This reverts commit 09a35b1a7e.
2024-04-26 18:28:12 +02:00
l10n daemon script
0cde5d6168 GIT_SILENT Sync po/docbooks with svn 2024-04-26 02:54:51 +00:00
l10n daemon script
eeddf99ca5 GIT_SILENT Sync po/docbooks with svn 2024-04-25 02:59:02 +00:00
Joshua Goins
09a35b1a7e Preserve mx-reply in the edited message if it exists
(cherry picked from commit fa57db8e83)
2024-04-24 15:30:17 -04:00
l10n daemon script
533182ec55 GIT_SILENT Sync po/docbooks with svn 2024-04-24 03:37:34 +00:00
Tobias Fella
70a8842f00 Use escaped title in devtools
(cherry picked from commit 307536c6b6)
2024-04-23 14:04:21 +02:00
Tobias Fella
ab33d1ca88 Work around QML opening dialog in wrong window
(cherry picked from commit 203be8bd35)
2024-04-23 14:04:06 +02:00
Tobias Fella
9e45f22e09 Replace Quotient::Connection with NeoChatConnection where possible
(cherry picked from commit 1e644587b3)
2024-04-23 14:03:51 +02:00
James Graham
6a627dfff0 Refactor the MessageComponentModel component update
(cherry picked from commit 66a60f09e3)
2024-04-23 14:03:37 +02:00
Tobias Fella
a9f05a7f63 Remove search bar; Use QuickSwitcher instead
(cherry picked from commit 69b6f16ec1)
2024-04-23 14:03:26 +02:00
James Graham
4dfd4b68eb Fix Roomlist Shortcuts
Fix the ctrl + pgup/pgdwn shortcuts for the room list so that they work with tree model

BUG: 485949
(cherry picked from commit 28c9d94457)
2024-04-23 14:03:19 +02:00
Tobias Fella
3786710d81 Force author display name in HiddenDelegate to PlainText
(cherry picked from commit d74fd1a560)
2024-04-23 14:03:13 +02:00
James Graham
3967b27352 Make the SpaceDrawer navigable with the keyboard.
(cherry picked from commit 624b1b06c5)
2024-04-23 14:03:05 +02:00
Carl Schwan
714ea8413c Apply 1 suggestion(s) to 1 file(s)
Co-authored-by: Carl Schwan <carl@carlschwan.eu>
(cherry picked from commit 95376c2ccc)
2024-04-23 14:02:53 +02:00
James Graham
4097addae9 Use AvatarButton in UserInfo instead of a custom button. This has the advantage of showing keyboard focus properly
(cherry picked from commit 1eb622165b)
2024-04-23 14:02:42 +02:00
Nate Graham
e9ac9deb40 Use more appropriate icons and tooltips for the room info drawer handles
Right now they use the standard text but left and right arrow icons,
which is a bit odd, and I think fails to convey what will happen when
clicked especially whern the drawer is closed.

Instead, let's use descriptive tooltip text for both, and a descriptive
icon for the the "this will open the drawer" handle button. For the one
to close the drawer, the default icon seems better, so let's stop
overriding it.

(cherry picked from commit 9d6ba324fb)
2024-04-23 14:02:28 +02:00
James Graham
3b858ab7d5 Use new cornerRadius Kirigami unit across the app
(cherry picked from commit ab0c8b8170)
2024-04-23 14:02:19 +02:00
James Graham
08807797a5 Make sure that tab can be used to navigate away from the chatbar
(cherry picked from commit 91d295e0bb)
2024-04-23 14:02:09 +02:00
James Graham
923839d6c7 Add Carl's focus title hack as a devtool option
(cherry picked from commit 125974dd7a)
2024-04-23 14:02:01 +02:00
Tobias Fella
3d4a1d22b0 Improve CodeComponent background
(cherry picked from commit 92895a7d00)
2024-04-23 14:01:53 +02:00
Nate Graham
5aa7f499c0 Make the "add new" menu button a hamburger menu
I know hamburger menus sometimes aren't amazing, but the current icon is
misleading. It's a plus button which generally means "create new".
However the menu is full of actions not related to creating new things,
including:

- Explore Rooms
- Find your Friends
- Scan a QR Code

These actions may technically result in a new room appearing in the
sidebar, but that's not a user's definition of creating a new thing;
these are *joining* a thing, and the fact that a new entry appears in
the sidebar is an implementation detail.

As a result the existing icon is inaccurate, and also holds back the
menu from adding additional items in the future that are even less
related to creating new rooms. An example would be the quick room
switcher, which is not exposed visibly in the UI anywhere, and could not
logically live in the current menu without changing its icon and text.

(cherry picked from commit d9308440e6)
2024-04-23 14:01:45 +02:00
James Graham
40c3519737 Change actionChanged to notificationActionChanged
Change actionChanged to notificationActionChanged to avoid any clashes with ItemDelegate action property signals

(cherry picked from commit 5340142c06)
2024-04-23 14:01:36 +02:00
James Graham
6ec9cc2475 Elide the Hidden delegate text
(cherry picked from commit 012d30ee9f)
2024-04-23 14:01:26 +02:00
James Graham
eba34b19ad Only override the DelegateType when showing hidden messages
(cherry picked from commit 031d69d996)
2024-04-23 14:01:16 +02:00
James Graham
8517636485 Implement devtoool to show hidden timeline messages
(cherry picked from commit 8b63c18f65)
2024-04-23 14:01:07 +02:00
James Graham
4a96dae57d Fancy Effects 2021-2024 gone but never forgotten
Remove fancy effects as it's busted and causing CPU spikes.

(cherry picked from commit dc2f11eb2b)
2024-04-23 14:00:57 +02:00
James Graham
09f433be45 Use 0.8.x for libQuotient flatpak
(cherry picked from commit 13e64a9487)
2024-04-23 14:00:44 +02:00
l10n daemon script
b9901a9167 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"
2024-04-23 03:20:13 +00:00
l10n daemon script
8b27d99d82 GIT_SILENT made messages (after extraction) 2024-04-23 02:45:40 +00:00
l10n daemon script
6b53c4d7b1 GIT_SILENT Sync po/docbooks with svn 2024-04-22 03:49:36 +00:00
l10n daemon script
bd28a7f66d 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"
2024-04-22 03:40:01 +00:00
l10n daemon script
0d1c09696d GIT_SILENT made messages (after extraction) 2024-04-22 03:04:59 +00:00
Albert Astals Cid
aeb4013d26 GIT_SILENT Upgrade release service version to 24.04.80. 2024-04-21 11:39:05 +02:00
182 changed files with 23235 additions and 37975 deletions

View File

@@ -40,4 +40,4 @@ Dependencies:
Options:
per-test-timeout: 90
require-passing-tests-on: [ '@all' ]
require-passing-tests-on: [ 'Linux', 'Android', 'FreeBSD' ]

View File

@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "07")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION_MINOR "05")
set(RELEASE_SERVICE_VERSION_MICRO "2")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})

View File

@@ -1,56 +0,0 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
import os
import subprocess
import sys
import unittest
import time
from appium import webdriver
from appium.options.common.base import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
class CreateRoomTest(unittest.TestCase):
mockServerProcess: subprocess.Popen
@classmethod
def setUpClass(cls):
cls.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
options = AppiumOptions()
options.set_capability("app", "neochat --ignore-ssl-errors --test")
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
def setUp(self):
pass
def tearDown(self):
if not self._outcome.result.wasSuccessful():
self.driver.get_screenshot_as_file("failed_test_shot_{}.png".format(self.id()))
@classmethod
def tearDownClass(self):
self.mockServerProcess.terminate()
self.driver.quit()
def test_create_room(self):
self.driver.find_element(by=AppiumBy.NAME, value="@user:localhost:1234").click()
self.driver.find_element(by=AppiumBy.NAME, value="Show Menu").click()
self.driver.find_element(by=AppiumBy.NAME, value="Create a Room").click()
self.driver.find_element(by=AppiumBy.NAME, value="Name:").send_keys("Super awesome room name")#
time.sleep(0.1) # without this, the second half of the text is sent to the topic field?!
self.driver.find_element(by=AppiumBy.NAME, value="Topic:").send_keys("There are not enough raccoons here")
time.sleep(0.1)
self.driver.find_element(by=AppiumBy.NAME, value="Create Room").click()
time.sleep(0.1)
self.driver.find_element(by=AppiumBy.NAME, value="Super awesome room name").click()
self.driver.find_element(by=AppiumBy.NAME, value="Show Room Information").click()
self.driver.find_element(by=AppiumBy.NAME, value="There are not enough raccoons here")
if __name__ == '__main__':
unittest.main()

View File

@@ -1,78 +0,0 @@
{
"next_batch": "batch1234",
"rooms": {
"join": {
"!newroom123321:localhost:1234": {
"state": {
"events": [
{
"type": "m.room.member",
"state_key": "@user:localhost:1234",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_0:localhost:1234",
"room_id": "!newroom123321:localhost:1234",
"content": {
"avatar_url": "",
"displayname": "A Display Name",
"membership": "join",
"reason": "Nothing"
},
"unsigned": {
"age": 1234
}
},
{
"type": "m.room.name",
"state_key": "",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_1:localhost:1234",
"room_id": "!newroom123321:localhost:1234",
"content": {
"name": "Super awesome room name"
},
"unsigned": {
"age": 1234
}
},
{
"type": "m.room.topic",
"state_key": "",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_2:localhost:1234",
"room_id": "!newroom123321:localhost:1234",
"content": {
"topic": "There are not enough raccoons here"
},
"unsigned": {
"age": 1234
}
}
]
},
"timeline": {
"events": [
{
"type": "m.room.message",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_1:localhost:1234",
"room_id": "!newroom123321:localhost:1234",
"content": {
"body": "This is a message",
"format": "org.matrix.custom.html",
"formatted_body": "<a href=\"https://matrix.to/#/@user:localhost:1234\">User</a>:",
"msgtype": "m.text"
},
"unsigned": {
"age": 1234
}
}
]
}
}
}
}
}

View File

@@ -6,8 +6,6 @@ from flask import Flask, request, abort
import os
app = Flask(__name__)
next_sync_payload = ""
@app.route("/_matrix/client/v3/login", methods=["GET"])
def login_get():
@@ -44,13 +42,8 @@ def load_json(name):
@app.route("/_matrix/client/r0/sync")
def sync():
global next_sync_payload
result = dict()
if len(next_sync_payload) > 0:
result = load_json(next_sync_payload)
next_sync_payload = ""
else:
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
return result
@app.route("/.well-known/matrix/client")
@@ -72,18 +65,6 @@ def upload_keys():
reply = dict()
return reply
@app.route("/_matrix/client/v3/createRoom", methods=["POST"])
def create_room():
global next_sync_payload
data = request.get_json()
if data["name"] != "Super awesome room name" or data["topic"] != "There are not enough raccoons here":
return dict(), 400
response = dict()
response["room_id"] = "!newroom123321:localhost:1234"
next_sync_payload = "sync_response_new_room"
return response
if __name__ == "__main__":
app.run(ssl_context='adhoc', port=1234)

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "https://matrix.to/#/@alice:example.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "mxc://example.org/SEsfnsuifSDFSSEF",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "testhttps://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "https://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "www.example.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,16 @@
{
"content": {
"body": "[Rich Link](https://kde.org)",
"format": "org.matrix.custom.html",
"formatted_body": "<a href=\"https://kde.org\">Rich Link</a>",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -56,7 +56,6 @@ private Q_SLOTS:
void genericBody();
void nullGenericBody();
void markdownBody();
void markdownBodyReply();
void subtitle();
void nullSubtitle();
void mediaInfo();
@@ -302,13 +301,6 @@ void EventHandlerTest::markdownBody()
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
}
void EventHandlerTest::markdownBodyReply()
{
EventHandler eventHandler(room, room->messageEvents().at(5).get());
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("reply"));
}
void EventHandlerTest::subtitle()
{
EventHandler eventHandler(room, room->messageEvents().at(0).get());

View File

@@ -6,11 +6,12 @@
#include "linkpreviewer.h"
#include "utils.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "utils.h"
#include "testutils.h"
using namespace Quotient;
@@ -29,9 +30,6 @@ private Q_SLOTS:
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void multipleLinkPreviewsMatch_data();
void multipleLinkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
};
@@ -44,59 +42,45 @@ void LinkPreviewerTest::initTestCase()
void LinkPreviewerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("inputString");
QTest::addColumn<QString>("eventSource");
QTest::addColumn<QUrl>("testOutputLink");
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttpsLinkDescription") << QStringLiteral("<a href=\"https://kde.org\">https://kde.org</a>") << QUrl("https://kde.org"_ls);
QTest::newRow("plainHttps") << QStringLiteral("test-validplainlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttps") << QStringLiteral("test-validrichlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("plainWww") << QStringLiteral("test-validplainwwwlink-event.json") << QUrl("www.example.org"_ls);
QTest::newRow("multipleHttps") << QStringLiteral("test-multiplelink-event.json") << QUrl("www.example.org"_ls);
}
void LinkPreviewerTest::linkPreviewsMatch()
{
QFETCH(QString, inputString);
QFETCH(QString, eventSource);
QFETCH(QUrl, testOutputLink);
auto link = LinkPreviewer::linkPreviews(inputString)[0];
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(LinkPreviewer::linkPreview(event.get()), connection);
QCOMPARE(link, testOutputLink);
}
void LinkPreviewerTest::multipleLinkPreviewsMatch_data()
{
QTest::addColumn<QString>("inputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("multipleHttps") << QStringLiteral("www.example.org https://kde.org") << QList{QUrl("www.example.org"_ls), QUrl("https://kde.org"_ls)};
QTest::newRow("multipleHttps1Invalid") << QStringLiteral("www.example.org mxc://example.org/SEsfnsuifSDFSSEF") << QList{QUrl("www.example.org"_ls)};
}
void LinkPreviewerTest::multipleLinkPreviewsMatch()
{
QFETCH(QString, inputString);
QFETCH(QList<QUrl>, testOutputLinks);
auto links = LinkPreviewer::linkPreviews(inputString);
QCOMPARE(links, testOutputLinks);
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), testOutputLink);
}
void LinkPreviewerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("inputString");
QTest::addColumn<QString>("eventSource");
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF");
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org");
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org");
QTest::newRow("mxc") << QStringLiteral("test-invalidmxclink-event.json");
QTest::newRow("matrixTo") << QStringLiteral("test-invalidmatrixtolink-event.json");
QTest::newRow("noSpace") << QStringLiteral("test-invalidnospacelink-event.json");
}
void LinkPreviewerTest::linkPreviewsReject()
{
QFETCH(QString, inputString);
QFETCH(QString, eventSource);
auto links = LinkPreviewer::linkPreviews(inputString);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(LinkPreviewer::linkPreview(event.get()), connection);
QCOMPARE(links.empty(), true);
QCOMPARE(linkPreviewer.empty(), true);
QCOMPARE(linkPreviewer.url(), QUrl());
}
QTEST_MAIN(LinkPreviewerTest)

View File

@@ -80,12 +80,14 @@
<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>
<p xml:lang="ar">نيوتشات هو تطبيق دردشة يتيح لك الاستفادة الكاملة من شبكة Matrix. فهو يوفر لك طريقة آمنة لإرسال الرسائل النصية ومقاطع الفيديو والملفات الصوتية إلى عائلتك وزملائك وأصدقائك.</p>
<p xml:lang="ca">El NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
<p xml:lang="ca-valencia">NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
<p xml:lang="en-GB">NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
<p xml:lang="eo">NeoChat estas babilej-apo, kiu ebligas al vi plene profiti de la Matrix-reto. Ĝi provizas al vi sekuran manieron sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj.</p>
<p xml:lang="es">NeoChat es una aplicación de chat que le permite aprovechar al máximo la red Matrix. Le proporciona un modo seguro de enviar mensajes de texto, vídeos y archivos de sonido a su familia, colegas y amigos.</p>
<p xml:lang="eu">NeoChat, Matrix sarearen abantaila guztiei probetsua ateratzeko aukera ematen dizun berriketa aplikaizo bat da. Zure familiari, kideei eta lagunei testu mezuak, bideoak eta audio fitxategiak era seguruan bidaltzeko aukera ematen dizu.</p>
<p xml:lang="fi">NeoChat on keskustelusovellus, jolla Matrix-verkosta saa täyden hyödyn. Se tarjoaa salatun kanavan lähettää perheelle, työkavereille ja ystäville tekstiviestejä sekä video- ja äänitiedostoja.</p>
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p>
<p xml:lang="hu">A NeoChat egy olyan csevegőalkalmazás, amellyel teljes mértékben kihasználhatja a Matrix hálózatot. Biztonságos módot biztosít szöveges üzenetek, videók és hangfájlok küldéséhez családtagjainak, kollégáinak és barátainak.</p>
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
@@ -93,8 +95,10 @@
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
<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="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>
@@ -111,7 +115,7 @@
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p>
<p xml:lang="ia">NeoChat aspira a esser un application plenemente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
<p xml:lang="ia">NeoChat aspira a esser un application plenmente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
<p xml:lang="ka">NeoChat მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
<p xml:lang="ko">NeoChat은 Matrix 표준을 따르는 프로그램을 목표로 합니다. 현재 안정 버전의 표준에서 제공하는 기능의 대부분을 지원하며, VoIP, 스레드, 일부 종단간 암호화와 같은 기능은 아직 지원하지 않습니다. Matrix 표준은 계속하여 진화 중이기 때문에 일부 기능이 빠져 있을 수도 있지만 장기적으로는 전체 표준을 지원하는 것이 목표입니다.</p>
@@ -210,7 +214,7 @@
<li xml:lang="sl">Sticker Packs - MSC2545</li>
<li xml:lang="sv">Sticker Packs - MSC2545</li>
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
<li xml:lang="tr">Yapışkan Paketleri — 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>
@@ -310,12 +314,14 @@
<screenshot type="default">
<image>https://cdn.kde.org/screenshots/neochat/spaces.png</image>
<caption>Discover new communities with Matrix Spaces</caption>
<caption xml:lang="ar">اكتشف مجتمعات جديدة مع فضاءات ماتركس</caption>
<caption xml:lang="ca">Descobriu comunitats noves amb els espais de Matrix</caption>
<caption xml:lang="ca-valencia">Descobriu comunitats noves amb els espais de Matrix</caption>
<caption xml:lang="en-GB">Discover new communities with Matrix Spaces</caption>
<caption xml:lang="eo">Malkovru novajn komunumojn per Matrix Spaces</caption>
<caption xml:lang="es">Descubra nuevas comunidades con los espacios de Matrix</caption>
<caption xml:lang="eu">Ezagutu komunitate berriak Matrixeko Tokiak erabiliz</caption>
<caption xml:lang="fi">Löydä uusia yhteisöjä Matrix Spacesillä</caption>
<caption xml:lang="fr">Découvrez de nouvelles communautés avec les espaces sous Matrix</caption>
<caption xml:lang="hu">Fedezzen fel új közösségeket a Matrix Terek segítségével</caption>
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption>
@@ -323,8 +329,10 @@
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
<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="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
<caption xml:lang="sv">Upptäck nya gemenskaper med Matrix Spaces</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>
@@ -407,6 +415,8 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="24.05.2" date="2024-07-04"/>
<release version="24.05.1" date="2024-06-13"/>
<release version="24.05.0" date="2024-05-23"/>
<release version="24.02.2" date="2024-04-11"/>
<release version="24.02.1" date="2024-03-21"/>

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

@@ -77,7 +77,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
></term>
<listitem>
<para
>L'URI de Matrix per a un usuari o una sala. P. ex. matrix:u/usuari:exemple.org o matrix:r/root:exemple.org. Això farà que el NeoChat intenti obrir la sala o conversa indicada. </para>
>L'URI de Matrix per a un usuari o una sala. P. ex. matrix:u/usuari:example.org o matrix:r/root:example.org. Això farà que el NeoChat intenti obrir la sala o conversa indicada. </para>
</listitem>
</varlistentry>
</variablelist>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@ add_library(neochat STATIC
models/customemojimodel.h
clipboard.cpp
clipboard.h
matriximageprovider.cpp
matriximageprovider.h
models/messageeventmodel.cpp
models/messageeventmodel.h
models/messagefiltermodel.cpp
@@ -175,16 +177,6 @@ add_library(neochat STATIC
foreigntypes.h
models/threepidmodel.cpp
models/threepidmodel.h
threepidaddhelper.cpp
threepidaddhelper.h
jobs/neochatadd3pidjob.cpp
jobs/neochatadd3pidjob.h
identityserverhelper.cpp
identityserverhelper.h
enums/powerlevel.cpp
enums/powerlevel.h
models/permissionsmodel.cpp
models/permissionsmodel.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -215,10 +207,16 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/HoverActions.qml
qml/ChatBar.qml
qml/AttachmentPane.qml
qml/ReplyPane.qml
qml/CompletionMenu.qml
qml/PieProgressBar.qml
qml/QuickFormatBar.qml
qml/EmojiPicker.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/EmojiDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
@@ -238,6 +236,9 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/ConfirmEncryptionDialog.qml
qml/RemoveSheet.qml
qml/BanSheet.qml
qml/EmojiTonesPicker.qml
qml/EmojiDelegate.qml
qml/EmojiGrid.qml
qml/RoomSearchPage.qml
qml/LocationChooser.qml
qml/TimelineView.qml
@@ -261,6 +262,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/SelectParentDialog.qml
qml/QrCodeMaximizeComponent.qml
qml/SelectSpacesDialog.qml
qml/AttachDialog.qml
qml/NotificationsView.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
@@ -278,16 +280,12 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/ConfirmLeaveDialog.qml
qml/CodeMaximizeComponent.qml
qml/EditStateDialog.qml
qml/ConsentDialog.qml
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
)
add_subdirectory(settings)
add_subdirectory(timeline)
add_subdirectory(devtools)
add_subdirectory(login)
add_subdirectory(chatbar)
if(UNIX)
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
@@ -381,7 +379,7 @@ else()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin chatbarplugin)
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin)
target_link_libraries(neochat PUBLIC
Qt::Core
Qt::Quick

View File

@@ -1,19 +0,0 @@
# SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(chatbar STATIC)
qt_add_qml_module(chatbar
URI org.kde.neochat.chatbar
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/chatbar
QML_FILES
AttachDialog.qml
ChatBar.qml
CompletionMenu.qml
EmojiDelegate.qml
EmojiGrid.qml
ReplyPane.qml
PieProgressBar.qml
EmojiPicker.qml
EmojiDialog.qml
EmojiTonesPicker.qml
)

View File

@@ -9,20 +9,27 @@
#include <KLocalizedString>
#include <QGuiApplication>
#include <QNetworkProxy>
#include <QQuickTextDocument>
#include <QQuickWindow>
#include <QStandardPaths>
#include <QStringBuilder>
#include <QTimer>
#include <signal.h>
#include <Quotient/accountregistry.h>
#include <Quotient/csapi/logout.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/eventstats.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "proxycontroller.h"
#include "roommanager.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
@@ -38,10 +45,6 @@
#endif
#endif
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
bool testMode = false;
using namespace Quotient;
@@ -186,7 +189,7 @@ void Controller::invokeLogin()
m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged();
if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId};
QString accessToken;
@@ -214,11 +217,11 @@ void Controller::invokeLogin()
}
}
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QString &userId)
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
{
qDebug() << "Reading access token from the keychain for" << userId;
qDebug() << "Reading access token from the keychain for" << account.userId();
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
job->setKey(userId);
job->setKey(account.userId());
// Handling of errors
connect(job, &QKeychain::Job::finished, this, [this, job]() {
@@ -249,12 +252,12 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QStrin
return job;
}
bool Controller::saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken)
bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
{
qDebug() << "Save the access token to the keychain for " << userId;
qDebug() << "Save the access token to the keychain for " << account.userId();
QKeychain::WritePasswordJob job(qAppName());
job.setAutoDelete(false);
job.setKey(userId);
job.setKey(account.userId());
job.setBinaryData(accessToken);
QEventLoop loop;
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);

View File

@@ -5,9 +5,15 @@
#include <QObject>
#include <QQmlEngine>
#include <QQuickItem>
#include "neochatconnection.h"
#include <Quotient/accountregistry.h>
#include <Quotient/settings.h>
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
class TrayIcon;
class QQuickTextDocument;
@@ -76,7 +82,7 @@ public:
/**
* @brief Save an access token to the keychain for the given account.
*/
bool saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken);
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
[[nodiscard]] bool supportSystemTray() const;
@@ -104,7 +110,7 @@ private:
QPointer<NeoChatConnection> m_connection;
TrayIcon *m_trayIcon = nullptr;
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const QString &account);
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
void loadSettings();
void saveSettings() const;

View File

@@ -31,12 +31,12 @@ FormCard.FormCardPage {
implicitWidth: tabBar.tabWidth
}
QQC2.TabButton {
text: i18nc("@title:tab", "Room Data")
text: qsTr("Room Data")
implicitWidth: tabBar.tabWidth
}
QQC2.TabButton {
text: i18nc("@title:tab", "Server Info")
text: qsTr("Server Info")
implicitWidth: tabBar.tabWidth
}

View File

@@ -28,14 +28,5 @@ FormCard.FormCardPage {
onToggled: Config.secretBackup = checked
}
FormCard.FormCheckDelegate {
text: i18nc("@option:check Enable the matrix feature to add a phone number as a third party ID", "Add phone numbers as 3PIDs")
checked: Config.phone3PId
onToggled: {
Config.phone3PId = checked
Config.save();
}
}
}
}

View File

@@ -1,109 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "powerlevel.h"
QString PowerLevel::nameForLevel(Level level)
{
switch (level) {
case PowerLevel::Member:
return i18n("Member");
case PowerLevel::Moderator:
return i18n("Moderator");
case PowerLevel::Admin:
return i18n("Admin");
case PowerLevel::Mute:
return i18n("Mute");
case PowerLevel::Custom:
return i18n("Custom");
default:
return {};
}
}
int PowerLevel::valueForLevel(Level level)
{
switch (level) {
case PowerLevel::Member:
return 0;
case PowerLevel::Moderator:
return 50;
case PowerLevel::Admin:
return 100;
case PowerLevel::Mute:
return -1;
default:
return {};
}
}
PowerLevel::Level PowerLevel::levelForValue(int value)
{
switch (value) {
case 0:
return PowerLevel::Member;
case 50:
return PowerLevel::Moderator;
case 100:
return PowerLevel::Admin;
case -1:
return PowerLevel::Mute;
default:
return PowerLevel::Custom;
}
}
PowerLevelModel::PowerLevelModel(QObject *parent)
: QAbstractListModel(parent)
{
}
bool PowerLevelModel::showMute() const
{
return m_showMute;
}
void PowerLevelModel::setShowMute(bool showMute)
{
if (showMute == m_showMute) {
return;
}
m_showMute = showMute;
Q_EMIT showMuteChanged();
}
QVariant PowerLevelModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
if (index.row() >= rowCount()) {
qDebug() << "PowerLevelModel, something's wrong: index.row() >= m_rules.count()";
return {};
}
const auto level = static_cast<PowerLevel::Level>(index.row());
if (role == NameRole) {
return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.",
"%1 (%2)",
PowerLevel::nameForLevel(level),
PowerLevel::valueForLevel(level));
}
if (role == ValueRole) {
return PowerLevel::valueForLevel(level);
}
return {};
}
int PowerLevelModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return PowerLevel::NUMLevels - (m_showMute ? 0 : 1);
}
QHash<int, QByteArray> PowerLevelModel::roleNames() const
{
return {{NameRole, "name"}, {ValueRole, "value"}};
}
#include "moc_powerlevel.cpp"

View File

@@ -1,110 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QAbstractListModel>
#include <QObject>
#include <QQmlEngine>
#include <KLocalizedString>
/**
* @class PowerLevel
*
* This class is designed to define the PowerLevel enumeration.
*/
class PowerLevel : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief The type of delegate that is needed for the event.
*
* @note While similar this is not the matrix event or message type. This is
* to tell a QML ListView what delegate to show for each event. So while
* similar to the spec it is not the same.
*/
enum Level {
Member, /**< A basic member. */
Moderator, /**< A moderator with enhanced powers. */
Admin, /**< The highest power level in the room. */
Mute, /**< The level to remove posting privileges. */
NUMLevels,
Custom, /**< A non-standard value. Intentionally after NUMLevels so it doesn't appear in the model. */
};
Q_ENUM(Level);
/**
* @brief Return a string representation of the enum value.
*/
static QString nameForLevel(Level level);
/**
* @brief Return the integer representation of the enum value.
*/
static int valueForLevel(Level level);
/**
* @brief Return the enum value for the given integer power level.
*/
static Level levelForValue(int value);
};
/**
* @class PowerLevelModel
*
* A model visualize the allowed power levels.
*/
class PowerLevelModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(bool showMute READ showMute WRITE setShowMute NOTIFY showMuteChanged)
public:
/**
* @brief Defines the model roles.
*/
enum Roles {
NameRole = Qt::DisplayRole, /**< The power level name. */
ValueRole, /**< The power level value. */
};
Q_ENUM(Roles)
explicit PowerLevelModel(QObject *parent = nullptr);
[[nodiscard]] bool showMute() const;
void setShowMute(bool showMute);
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a mapping from Role enum values to role names.
*
* @sa Roles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
void showMuteChanged();
private:
bool m_showMute = true;
};

View File

@@ -293,10 +293,7 @@ QString EventHandler::getMarkdownBody() const
}
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
QString plainBody = roomMessageEvent->plainBody();
plainBody.remove(TextRegex::removeReply);
return plainBody;
return roomMessageEvent->plainBody();
}
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const

View File

@@ -33,4 +33,4 @@ FakeRunner::FakeRunner()
qDBusRegisterMetaType<RemoteImage>();
}
#include "moc_fakerunner.cpp"
#include "moc_fakerunner.cpp"

View File

@@ -1,156 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "identityserverhelper.h"
#include <QNetworkReply>
#include <KLocalizedString>
#include <Quotient/networkaccessmanager.h>
#include "neochatconnection.h"
IdentityServerHelper::IdentityServerHelper(QObject *parent)
: QObject(parent)
{
}
NeoChatConnection *IdentityServerHelper::connection() const
{
return m_connection;
}
void IdentityServerHelper::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
if (m_connection != nullptr) {
m_connection->disconnect(this);
}
m_connection = connection;
Q_EMIT connectionChanged();
Q_EMIT currentServerChanged();
connect(m_connection, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
if (type == QLatin1String("m.identity_server")) {
Q_EMIT currentServerChanged();
}
});
}
QString IdentityServerHelper::currentServer() const
{
if (m_connection == nullptr) {
return {};
}
if (!m_connection->hasAccountData(QLatin1String("m.identity_server"))) {
return i18nc("@info", "No identity server configured");
}
const auto url = m_connection->accountData(QLatin1String("m.identity_server"))->contentPart<QUrl>(QLatin1String("base_url"));
if (!url.isEmpty()) {
return url.toString();
}
return i18nc("@info", "No identity server configured");
}
bool IdentityServerHelper::hasCurrentServer() const
{
if (m_connection == nullptr && !m_connection->hasAccountData(QLatin1String("m.identity_server"))) {
return false;
}
const auto url = m_connection->accountData(QLatin1String("m.identity_server"))->contentPart<QUrl>(QLatin1String("base_url"));
if (!url.isEmpty()) {
return true;
}
return false;
}
QString IdentityServerHelper::url() const
{
return m_url;
}
void IdentityServerHelper::setUrl(const QString &url)
{
if (url == m_url) {
return;
}
m_url = url;
Q_EMIT urlChanged();
checkUrl();
}
IdentityServerHelper::IdServerStatus IdentityServerHelper::status() const
{
return m_status;
}
void IdentityServerHelper::checkUrl()
{
if (m_idServerCheckRequest != nullptr) {
m_idServerCheckRequest->abort();
m_idServerCheckRequest.clear();
}
if (m_url == currentServer()) {
m_status = Match;
Q_EMIT statusChanged();
return;
}
if (m_url.isEmpty()) {
m_status = Valid;
Q_EMIT statusChanged();
return;
}
const auto requestUrl = QUrl(m_url + QStringLiteral("/_matrix/identity/v2"));
if (!(requestUrl.scheme() == QStringLiteral("https") || requestUrl.scheme() == QStringLiteral("http"))) {
m_status = Invalid;
Q_EMIT statusChanged();
return;
}
QNetworkRequest request(requestUrl);
m_idServerCheckRequest = Quotient::NetworkAccessManager::instance()->get(request);
connect(m_idServerCheckRequest, &QNetworkReply::finished, this, [this]() {
if (m_idServerCheckRequest->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
m_status = Valid;
Q_EMIT statusChanged();
} else {
m_status = Invalid;
Q_EMIT statusChanged();
}
});
}
void IdentityServerHelper::setIdentityServer()
{
if (m_url == currentServer()) {
return;
}
m_connection->setAccountData(QLatin1String("m.identity_server"), {{QLatin1String("base_url"), m_url}});
m_status = Ready;
Q_EMIT statusChanged();
}
void IdentityServerHelper::clearIdentityServer()
{
if (currentServer().isEmpty()) {
return;
}
m_connection->setAccountData(QLatin1String("m.identity_server"), {{QLatin1String("base_url"), QString()}});
m_status = Ready;
Q_EMIT statusChanged();
}
#include "moc_identityserverhelper.cpp"

View File

@@ -1,103 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <Quotient/jobs/basejob.h>
class NeoChatConnection;
/**
* @class IdentityServerHelper
*
* This class is designed to help the process of setting an identity server for the account.
* It will manage the various stages of verification and authentication.
*/
class IdentityServerHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The connection to add a 3PID to.
*/
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/**
* @brief The current identity server.
*/
Q_PROPERTY(QString currentServer READ currentServer NOTIFY currentServerChanged)
/**
* @brief Whether an identity server is currently configured.
*/
Q_PROPERTY(bool hasCurrentServer READ hasCurrentServer NOTIFY currentServerChanged)
/**
* @brief The URL for the desired server.
*/
Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
/**
* @brief The current status.
*/
Q_PROPERTY(IdServerStatus status READ status NOTIFY statusChanged)
public:
/**
* @brief The current status for adding an identity server
*/
enum IdServerStatus {
Ready, /**< The process is ready to start. I.e. there is no ongoing attempt to set a new server. */
Valid, /**< The server URL is valid. */
Invalid, /**< The server URL is invalid. */
Match, /**< The server URL is the one that is already configured. */
Other, /**< An unknown problem occurred. */
};
Q_ENUM(IdServerStatus)
explicit IdentityServerHelper(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
[[nodiscard]] QString currentServer() const;
[[nodiscard]] bool hasCurrentServer() const;
[[nodiscard]] QString url() const;
void setUrl(const QString &url);
[[nodiscard]] IdServerStatus status() const;
/**
* @brief Set the current URL as the user's identity server.
*
* Will do nothing if the URL isn't a valid identity server.
*/
Q_INVOKABLE void setIdentityServer();
/**
* @brief Clear the user's identity server.
*/
Q_INVOKABLE void clearIdentityServer();
Q_SIGNALS:
void connectionChanged();
void currentServerChanged();
void urlChanged();
void statusChanged();
private:
QPointer<NeoChatConnection> m_connection;
IdServerStatus m_status = Ready;
QString m_url;
QPointer<QNetworkReply> m_idServerCheckRequest;
void checkUrl();
};

View File

@@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "neochatadd3pidjob.h"
using namespace Quotient;
NeochatAdd3PIdJob::NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Omittable<QJsonObject> &auth)
: BaseJob(HttpVerb::Post, QStringLiteral("Add3PIDJob"), makePath("/_matrix/client/v3", "/account/3pid/add"))
{
QJsonObject _dataJson;
addParam<IfNotEmpty>(_dataJson, QStringLiteral("auth"), auth);
addParam<>(_dataJson, QStringLiteral("client_secret"), clientSecret);
addParam<>(_dataJson, QStringLiteral("sid"), sid);
setRequestData({_dataJson});
}

View File

@@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <Quotient/jobs/basejob.h>
#include <Quotient/omittable.h>
class NeochatAdd3PIdJob : public Quotient::BaseJob
{
public:
explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
};

View File

@@ -89,23 +89,38 @@ bool LinkPreviewer::empty() const
return m_url.isEmpty();
}
QList<QUrl> LinkPreviewer::linkPreviews(QString string)
QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
{
auto data = string.remove(TextRegex::removeRichReply);
if (event == nullptr) {
return {};
}
QString text;
if (event->hasTextContent()) {
auto textContent = static_cast<const Quotient::EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
auto data = text.remove(TextRegex::removeRichReply);
auto linksMatch = TextRegex::url.globalMatch(data);
QList<QUrl> links;
while (linksMatch.hasNext()) {
auto link = linksMatch.next().captured();
if (!link.contains(QStringLiteral("matrix.to")) && !links.contains(QUrl(link))) {
links += QUrl(link);
if (!link.contains(QStringLiteral("matrix.to"))) {
return QUrl(link);
}
}
return links;
return {};
}
bool LinkPreviewer::hasPreviewableLinks(const QString &string)
bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
{
return !linkPreviews(string).isEmpty();
return !linkPreview(event).isEmpty();
}
#include "moc_linkpreviewer.cpp"

View File

@@ -7,6 +7,11 @@
#include <QQmlEngine>
#include <QUrl>
namespace Quotient
{
class RoomMessageEvent;
}
class NeoChatRoom;
/**
@@ -66,19 +71,19 @@ public:
[[nodiscard]] bool empty() const;
/**
* @brief Whether the given string has at least 1 pre-viewable link.
* @brief Whether the given event has at least 1 pre-viewable link.
*
* A link is only pre-viewable if it is http, https or something starting with www.
*/
static bool hasPreviewableLinks(const QString &string);
static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
/**
* @brief Return previewable links from the given string.
* @brief Return the link to be previewed from the given event.
*
* This function is designed to give only links that should be previewed so
* http, https or something starting with www. The first valid link is returned.
*/
static QList<QUrl> linkPreviews(QString string);
static QUrl linkPreview(const Quotient::RoomMessageEvent *event);
private:
bool m_loaded;

View File

@@ -5,7 +5,6 @@
#include <Quotient/accountregistry.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "controller.h"
@@ -73,7 +72,7 @@ void LoginHelper::init()
account.setHomeserver(m_connection->homeserver());
account.setDeviceId(m_connection->deviceId());
account.setDeviceName(m_deviceName);
if (!Controller::instance().saveAccessTokenToKeyChain(account.userId(), m_connection->accessToken())) {
if (!Controller::instance().saveAccessTokenToKeyChain(account, m_connection->accessToken())) {
qWarning() << "Couldn't save access token";
}
account.sync();

View File

@@ -5,7 +5,6 @@
#include <QIcon>
#include <QNetworkDiskCache>
#include <QNetworkProxyFactory>
#include <QNetworkReply>
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlContext>
@@ -47,10 +46,11 @@
#include "colorschemer.h"
#include "controller.h"
#include "logger.h"
#include "matriximageprovider.h"
#include "neochatconfig.h"
#include "roommanager.h"
#include "sharehandler.h"
#include "windowcontroller.h"
#include "sharehandler.h"
#ifdef HAVE_RUNNER
#include "runner.h"
@@ -236,7 +236,6 @@ int main(int argc, char *argv[])
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();
@@ -286,6 +285,7 @@ int main(int argc, char *argv[])
ShareHandler::instance().setText(parser.value(shareOption));
}
engine.addImageProvider(QLatin1String("mxc"), MatrixImageProvider::create(&engine, &engine));
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
engine.loadFromModule("org.kde.neochat", "Main");

View File

@@ -37,7 +37,7 @@ QVariant AccountEmoticonModel::data(const QModelIndex &index, int role) const
const auto &row = index.row();
const auto &image = m_images->images[row];
if (role == UrlRole) {
return m_connection->makeMediaUrl(image.url).toString();
return m_connection->makeMediaUrl(image.url);
}
if (role == BodyRole) {
if (image.body) {
@@ -163,11 +163,7 @@ void AccountEmoticonModel::setEmoticonImage(int index, const QUrl &source)
QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl source)
{
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
#if Quotient_VERSION_MINOR > 8
co_await qCoro(job.get(), &BaseJob::finished);
#else
co_await qCoro(job, &BaseJob::finished);
#endif
if (job->error() != BaseJob::NoError) {
co_return;
}
@@ -189,11 +185,7 @@ QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl sourc
QCoro::Task<void> AccountEmoticonModel::doAddEmoticon(QUrl source, QString shortcode, QString description, QString type)
{
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
#if Quotient_VERSION_MINOR > 8
co_await qCoro(job.get(), &BaseJob::finished);
#else
co_await qCoro(job, &BaseJob::finished);
#endif
if (job->error() != BaseJob::NoError) {
co_return;
}

View File

@@ -153,13 +153,13 @@ QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
switch (Roles(role)) {
case Roles::ModelData:
return QVariant::fromValue(Emoji(m_connection->makeMediaUrl(QUrl(data.url)).toString(), data.name, true));
return QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + data.url.mid(6), data.name, true));
case Roles::Name:
case Roles::DisplayRole:
case Roles::ReplacedTextRole:
return data.name;
case Roles::ImageURL:
return m_connection->makeMediaUrl(QUrl(data.url));
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
case Roles::MxcUrl:
return m_connection->makeMediaUrl(QUrl(data.url));
default:
@@ -205,7 +205,7 @@ QVariantList CustomEmojiModel::filterModel(const QString &filter)
if (!emoji.name.contains(filter, Qt::CaseInsensitive))
continue;
results << QVariant::fromValue(Emoji(m_connection->makeMediaUrl(QUrl(emoji.url)).toString(), emoji.name, true));
results << QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + emoji.url.mid(6), emoji.name, true));
}
return results;
}

View File

@@ -128,12 +128,8 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
authData["type"_ls] = "m.login.password"_ls;
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, m_connection->user()->id()}};
authData["identifier"_ls] = identifier;
auto innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
#if Quotient_VERSION_MINOR > 8
connect(innerJob.get(), &BaseJob::success, this, onSuccess);
#else
auto *innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
connect(innerJob, &BaseJob::success, this, onSuccess);
#endif
} else {
onSuccess();
}

View File

@@ -110,14 +110,11 @@ MessageContentModel::MessageContentModel(const Quotient::RoomEvent *event, NeoCh
endResetModel();
}
});
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
updateComponents();
});
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
updateComponents();
});
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, &MessageContentModel::updateLinkPreviewer);
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &MessageContentModel::updateLinkPreviewer);
}
updateLinkPreviewer();
updateComponents();
}
@@ -200,9 +197,8 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return eventHandler.getReplyMediaInfo();
}
if (role == LinkPreviewerRole) {
if (component.type == MessageComponentType::LinkPreview) {
return QVariant::fromValue<LinkPreviewer *>(
dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(component.attributes["link"_ls].toUrl()));
if (m_linkPreviewer != nullptr) {
return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewer);
} else {
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
}
@@ -276,8 +272,12 @@ void MessageContentModel::updateComponents(bool isEditing)
m_components.append(componentsForType(eventHandler.messageComponentType()));
}
if (m_room->urlPreviewEnabled()) {
addLinkPreviews();
if (m_linkPreviewer != nullptr) {
if (m_linkPreviewer->loaded()) {
m_components += MessageComponent{MessageComponentType::LinkPreview, QString(), {}};
} else {
m_components += MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {}};
}
}
endResetModel();
@@ -294,9 +294,6 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
case MessageComponentType::File: {
QList<MessageComponent> components;
components += MessageComponent{MessageComponentType::File, QString(), {}};
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
auto body = EventHandler::rawMessageBody(*event);
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
if (m_emptyItinerary) {
auto fileTransferInfo = fileInfo();
@@ -324,77 +321,46 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
}
return components;
}
case MessageComponentType::Image:
case MessageComponentType::Video: {
if (!m_event->is<StickerEvent>()) {
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
QList<MessageComponent> components;
components += MessageComponent{type, QString(), {}};
auto body = EventHandler::rawMessageBody(*event);
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
return components;
}
}
default:
return {MessageComponent{type, QString(), {}}};
}
}
MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
void MessageContentModel::updateLinkPreviewer()
{
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
if (linkPreviewer == nullptr) {
return {};
if (m_room == nullptr || m_event == nullptr) {
if (m_linkPreviewer != nullptr) {
m_linkPreviewer->disconnect(this);
m_linkPreviewer = nullptr;
updateComponents();
}
return;
}
if (linkPreviewer->loaded()) {
return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_ls, link}}};
} else {
connect(linkPreviewer, &LinkPreviewer::loadedChanged, [this, link]() {
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
for (auto &component : m_components) {
if (component.attributes["link"_ls].toUrl() == link) {
if (!m_room->urlPreviewEnabled()) {
if (m_linkPreviewer != nullptr) {
m_linkPreviewer->disconnect(this);
m_linkPreviewer = nullptr;
updateComponents();
}
return;
}
if (const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
if (LinkPreviewer::hasPreviewableLinks(event)) {
m_linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(LinkPreviewer::linkPreview(event));
updateComponents();
if (m_linkPreviewer != nullptr) {
connect(m_linkPreviewer, &LinkPreviewer::loadedChanged, [this]() {
if (m_linkPreviewer != nullptr && m_linkPreviewer->loaded()) {
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
beginResetModel();
component.type = MessageComponentType::LinkPreview;
m_components[m_components.size() - 1].type = MessageComponentType::LinkPreview;
endResetModel();
}
}
}
});
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_ls, link}}};
}
}
void MessageContentModel::addLinkPreviews()
{
int i = 0;
while (i < m_components.size()) {
const auto component = m_components.at(i);
if (component.type == MessageComponentType::Text || component.type == MessageComponentType::Quote) {
if (LinkPreviewer::hasPreviewableLinks(component.content)) {
const auto links = LinkPreviewer::linkPreviews(component.content);
for (qsizetype j = 0; j < links.size(); ++j) {
const auto linkPreview = linkPreviewComponent(links[j]);
if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) {
m_components.insert(i + j + 1, linkPreview);
}
};
});
}
}
i++;
}
}
void MessageContentModel::closeLinkPreview(int row)
{
if (m_components[row].type == MessageComponentType::LinkPreview || m_components[row].type == MessageComponentType::LinkPreviewLoad) {
beginResetModel();
m_removedLinkPreviews += m_components[row].attributes["link"_ls].toUrl();
m_components.remove(row);
m_components.squeeze();
updateComponents();
endResetModel();
}
}

View File

@@ -11,9 +11,11 @@
#include "enums/messagecomponenttype.h"
#include "eventhandler.h"
#include "itinerarymodel.h"
#include "linkpreviewer.h"
#include "neochatroom.h"
struct MessageComponent {
MessageComponentType::Type type = MessageComponentType::Other;
MessageComponentType::Type type;
QString content;
QVariantMap attributes;
@@ -21,11 +23,6 @@ struct MessageComponent {
{
return type == right.type && content == right.content && attributes == right.attributes;
}
bool isEmpty() const
{
return type == MessageComponentType::Other;
}
};
/**
@@ -91,13 +88,6 @@ public:
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
/**
* @brief Close the link preview at the given index.
*
* If the given index is not a link preview component, nothing happens.
*/
Q_INVOKABLE void closeLinkPreview(int row);
private:
QPointer<NeoChatRoom> m_room;
const Quotient::RoomEvent *m_event = nullptr;
@@ -105,14 +95,12 @@ private:
QList<MessageComponent> m_components;
void updateComponents(bool isEditing = false);
QPointer<LinkPreviewer> m_linkPreviewer;
ItineraryModel *m_itineraryModel = nullptr;
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
MessageComponent linkPreviewComponent(const QUrl &link);
void addLinkPreviews();
QList<QUrl> m_removedLinkPreviews;
void updateLinkPreviewer();
void updateItineraryModel();
bool m_emptyItinerary = false;

View File

@@ -1,254 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "permissionsmodel.h"
#include "powerlevel.h"
#include <Quotient/events/roompowerlevelsevent.h>
#include <KLazyLocalizedString>
static const QStringList defaultPermissions = {
QStringLiteral("users_default"),
QStringLiteral("state_default"),
QStringLiteral("events_default"),
QStringLiteral("invite"),
QStringLiteral("kick"),
QStringLiteral("ban"),
QStringLiteral("redact"),
QStringLiteral("m.reaction"),
QStringLiteral("m.room.redaction"),
QStringLiteral("m.room.power_levels"),
QStringLiteral("m.room.name"),
QStringLiteral("m.room.avatar"),
QStringLiteral("m.room.canonical_alias"),
QStringLiteral("m.room.topic"),
QStringLiteral("m.room.encryption"),
QStringLiteral("m.room.history_visibility"),
QStringLiteral("m.room.pinned_events"),
QStringLiteral("m.room.tombstone"),
QStringLiteral("m.room.server_acl"),
QStringLiteral("m.space.child"),
QStringLiteral("m.space.parent"),
};
// Alternate name text for default permissions.
static const QHash<QString, KLazyLocalizedString> defaultPermissionNames = {
{QStringLiteral("users_default"), kli18nc("Room permission type", "Default user power level")},
{QStringLiteral("state_default"), kli18nc("Room permission type", "Default power level to set the room state")},
{QStringLiteral("events_default"), kli18nc("Room permission type", "Default power level to send messages")},
{QStringLiteral("invite"), kli18nc("Room permission type", "Invite users")},
{QStringLiteral("kick"), kli18nc("Room permission type", "Kick users")},
{QStringLiteral("ban"), kli18nc("Room permission type", "Ban users")},
{QStringLiteral("redact"), kli18nc("Room permission type", "Remove messages sent by other users")},
{QStringLiteral("m.reaction"), kli18nc("Room permission type", "Send reactions")},
{QStringLiteral("m.room.redaction"), kli18nc("Room permission type", "Remove their own messages")},
{QStringLiteral("m.room.power_levels"), kli18nc("Room permission type", "Change user permissions")},
{QStringLiteral("m.room.name"), kli18nc("Room permission type", "Change the room name")},
{QStringLiteral("m.room.avatar"), kli18nc("Room permission type", "Change the room avatar")},
{QStringLiteral("m.room.canonical_alias"), kli18nc("Room permission type", "Change the room canonical alias")},
{QStringLiteral("m.room.topic"), kli18nc("Room permission type", "Change the room topic")},
{QStringLiteral("m.room.encryption"), kli18nc("Room permission type", "Enable encryption for the room")},
{QStringLiteral("m.room.history_visibility"), kli18nc("Room permission type", "Change the room history visibility")},
{QStringLiteral("m.room.pinned_events"), kli18nc("Room permission type", "Set pinned events")},
{QStringLiteral("m.room.tombstone"), kli18nc("Room permission type", "Upgrade the room")},
{QStringLiteral("m.room.server_acl"), kli18nc("Room permission type", "Set the room server access control list (ACL)")},
{QStringLiteral("m.space.child"), kli18nc("Room permission type", "Set the children of this space")},
{QStringLiteral("m.space.parent"), kli18nc("Room permission type", "Set the parent space of this room")},
};
// Subtitles for the default values.
static const QHash<QString, KLazyLocalizedString> defaultSubtitles = {
{QStringLiteral("users_default"), kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
{QStringLiteral("state_default"), kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
{QStringLiteral("events_default"), kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
};
// Permissions that should use the event default.
static const QStringList eventPermissions = {
QStringLiteral("m.room.message"),
QStringLiteral("m.reaction"),
QStringLiteral("m.room.redaction"),
};
PermissionsModel::PermissionsModel(QObject *parent)
: QAbstractListModel(parent)
{
}
NeoChatRoom *PermissionsModel::room() const
{
return m_room;
}
void PermissionsModel::setRoom(NeoChatRoom *room)
{
if (room == m_room) {
return;
}
m_room = room;
Q_EMIT roomChanged();
initializeModel();
}
void PermissionsModel::initializeModel()
{
beginResetModel();
m_permissions.clear();
if (m_room == nullptr) {
endResetModel();
return;
}
const auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>();
if (currentPowerLevelEvent == nullptr) {
return;
}
m_permissions.append(defaultPermissions);
for (const auto &event : currentPowerLevelEvent->events().keys()) {
if (!m_permissions.contains(event)) {
m_permissions += event;
}
}
endResetModel();
}
QVariant PermissionsModel::data(const QModelIndex &index, int role) const
{
if (m_room == nullptr || !index.isValid()) {
return {};
}
if (index.row() >= rowCount()) {
qDebug() << "PushRuleModel, something's wrong: index.row() >= m_rules.count()";
return {};
}
const auto permission = m_permissions.value(index.row());
if (role == NameRole) {
if (defaultPermissionNames.keys().contains(permission)) {
return defaultPermissionNames.value(permission).toString();
}
return permission;
}
if (role == SubtitleRole) {
if (permission.startsWith(QLatin1String("m.")) && defaultPermissionNames.keys().contains(permission)) {
return permission;
}
if (defaultSubtitles.contains(permission)) {
return defaultSubtitles.value(permission).toString();
}
return QString();
}
if (role == TypeRole) {
return permission;
}
if (role == LevelRole) {
const auto level = powerLevel(permission);
if (level.has_value()) {
return *level;
}
return {};
}
if (role == LevelNameRole) {
const auto level = powerLevel(permission);
if (level.has_value()) {
return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.",
"%1 (%2)",
PowerLevel::nameForLevel(PowerLevel::levelForValue(*level)),
*level);
}
return QString();
}
if (role == IsDefaultValueRole) {
return permission.contains(QLatin1String("default"));
}
if (role == IsBasicPermissionRole) {
return permission == QStringLiteral("invite") || permission == QStringLiteral("kick") || permission == QStringLiteral("ban")
|| permission == QStringLiteral("redact");
}
return {};
}
int PermissionsModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_permissions.count();
}
QHash<int, QByteArray> PermissionsModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[NameRole] = "name";
roles[SubtitleRole] = "subtitle";
roles[TypeRole] = "type";
roles[LevelRole] = "level";
roles[LevelNameRole] = "levelName";
roles[IsDefaultValueRole] = "isDefaultValue";
roles[IsBasicPermissionRole] = "isBasicPermission";
return roles;
}
std::optional<int> PermissionsModel::powerLevel(const QString &permission) const
{
if (m_room == nullptr) {
return std::nullopt;
}
if (const auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>()) {
if (permission == QStringLiteral("ban")) {
return currentPowerLevelEvent->ban();
} else if (permission == QStringLiteral("kick")) {
return currentPowerLevelEvent->kick();
} else if (permission == QStringLiteral("invite")) {
return currentPowerLevelEvent->invite();
} else if (permission == QStringLiteral("redact")) {
return currentPowerLevelEvent->redact();
} else if (permission == QStringLiteral("users_default")) {
return currentPowerLevelEvent->usersDefault();
} else if (permission == QStringLiteral("state_default")) {
return currentPowerLevelEvent->stateDefault();
} else if (permission == QStringLiteral("events_default")) {
return currentPowerLevelEvent->eventsDefault();
} else if (eventPermissions.contains(permission)) {
return currentPowerLevelEvent->powerLevelForEvent(permission);
} else {
return currentPowerLevelEvent->powerLevelForState(permission);
}
}
return std::nullopt;
}
void PermissionsModel::setPowerLevel(const QString &permission, const int &newPowerLevel)
{
if (m_room == nullptr) {
return;
}
int clampPowerLevel = std::clamp(newPowerLevel, -1, 100);
const auto currentPowerLevel = powerLevel(permission);
if (!currentPowerLevel.has_value() || currentPowerLevel == clampPowerLevel) {
return;
}
if (auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>()) {
auto powerLevelContent = currentPowerLevelEvent->contentJson();
if (powerLevelContent.contains(permission)) {
powerLevelContent[permission] = clampPowerLevel;
} else {
auto eventPowerLevels = powerLevelContent[QLatin1String("events")].toObject();
eventPowerLevels[permission] = clampPowerLevel;
powerLevelContent[QLatin1String("events")] = eventPowerLevels;
}
m_room->setState<Quotient::RoomPowerLevelsEvent>(powerLevelContent);
}
}
#include "moc_permissionsmodel.cpp"

View File

@@ -1,87 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include <optional>
#include "neochatroom.h"
/**
* @class PermissionsModel
*
* This class defines the model for managing room permission levels.
*/
class PermissionsModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The room to show the permissions for
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
public:
/**
* @brief Defines the model roles.
*/
enum Roles {
NameRole = Qt::DisplayRole, /**< The permission name. */
SubtitleRole, /**< The description of the permission. */
TypeRole, /**< The base type of the permission, normally the event type id except for ban, kick, etc. */
LevelRole, /**< The current power level for the permission. */
LevelNameRole, /**< The current power level for the permission as a string. */
IsDefaultValueRole, /**< Whether the permission is a default value, e.g. for users. */
IsBasicPermissionRole, /**< Whether the permission is one of the basic ones, e.g. kick, ban, etc. */
};
Q_ENUM(Roles)
explicit PermissionsModel(QObject *parent = nullptr);
NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a mapping from Role enum values to role names.
*
* @sa Roles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
/**
* @brief Return the power level required for the given permission.
*/
std::optional<int> powerLevel(const QString &permission) const;
/**
* @brief Set the power level required for the given permission.
*/
Q_INVOKABLE void setPowerLevel(const QString &permission, const int &newPowerLevel);
Q_SIGNALS:
void roomChanged();
private:
QPointer<NeoChatRoom> m_room;
QStringList m_permissions;
void initializeModel();
};

View File

@@ -5,7 +5,6 @@
#include "neochatroom.h"
#include <QDebug>
#include <QFont>
#ifdef HAVE_ICU
#include <QTextBoundaryFinder>
#include <QTextCharFormat>

View File

@@ -4,7 +4,7 @@
#include "roomlistmodel.h"
#include "eventhandler.h"
#include "neochatconnection.h"
#include "neochatconfig.h"
#include "neochatroom.h"
#include "roommanager.h"
#include "spacehierarchycache.h"

View File

@@ -122,7 +122,7 @@ private:
QString m_searchText;
QPointer<NeoChatRoom> m_room;
std::optional<Quotient::SearchJob::ResultRoomEvents> m_result = std::nullopt;
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
Quotient::SearchJob *m_job = nullptr;
bool m_searching = false;
};

View File

@@ -10,7 +10,15 @@ ThreePIdModel::ThreePIdModel(NeoChatConnection *connection)
{
Q_ASSERT(connection);
connect(connection, &NeoChatConnection::stateChanged, this, [this]() {
refreshModel();
const auto connection = dynamic_cast<NeoChatConnection *>(this->parent());
if (connection != nullptr && connection->isLoggedIn()) {
const auto threePIdJob = connection->callApi<Quotient::GetAccount3PIDsJob>();
connect(threePIdJob, &Quotient::BaseJob::success, this, [this, threePIdJob]() {
beginResetModel();
m_threePIds = threePIdJob->threepids();
endResetModel();
});
}
});
}
@@ -48,17 +56,4 @@ QHash<int, QByteArray> ThreePIdModel::roleNames() const
};
}
void ThreePIdModel::refreshModel()
{
const auto connection = dynamic_cast<NeoChatConnection *>(this->parent());
if (connection != nullptr && connection->isLoggedIn()) {
const auto threePIdJob = connection->callApi<Quotient::GetAccount3PIDsJob>();
connect(threePIdJob, &Quotient::BaseJob::success, this, [this, threePIdJob]() {
beginResetModel();
m_threePIds = threePIdJob->threepids();
endResetModel();
});
}
}
#include "moc_threepidmodel.cpp"

View File

@@ -53,8 +53,6 @@ public:
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void refreshModel();
private:
QVector<Quotient::GetAccount3PIDsJob::ThirdPartyIdentifier> m_threePIds;
};

View File

@@ -7,7 +7,6 @@
#include <Quotient/events/roompowerlevelsevent.h>
#include "enums/powerlevel.h"
#include "neochatroom.h"
using namespace Quotient;
@@ -95,10 +94,16 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
auto userPl = pl->powerLevelForUser(user->id());
return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.",
"%1 (%2)",
PowerLevel::nameForLevel(PowerLevel::levelForValue(userPl)),
userPl);
switch (userPl) {
case 0:
return QStringLiteral("Member");
case 50:
return QStringLiteral("Moderator");
case 100:
return QStringLiteral("Admin");
default:
return QStringLiteral("Custom");
}
}
return {};

View File

@@ -202,6 +202,7 @@ Name[pl]=Nowe zaproszenie
Name[pt]=Novo Convite
Name[pt_BR]=Novo convite
Name[ru]=Новое приглашение
Name[sk]=Nová pozvánka
Name[sl]=Novo povabilo
Name[sv]=Ny inbjudan
Name[ta]=புதிய அழைப்பிதழ்
@@ -241,6 +242,7 @@ 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[ru]=Доступно новое приглашение в комнату
Comment[sk]=Máte novú pozvánku do miestnosti
Comment[sl]=Tam je novo povabilo v sobo
Comment[sv]=Det finns en ny inbjudan till ett rum
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
@@ -253,13 +255,15 @@ Action=Popup
[Event/Share]
Name=Share
Name[ar]=شارك
Name[ca]=Compartició
Name[ca@valencia]=Compartició
Name[ca@valencia]=Compartiu
Name[cs]=Sdílet
Name[en_GB]=Share
Name[eo]=Kundividi
Name[es]=Compartir
Name[eu]=Partekatu
Name[fi]=Jaa
Name[fr]=Partager
Name[hu]=Megosztás
Name[ia]=Comparti
@@ -267,20 +271,25 @@ Name[it]=Condivisione
Name[ka]=გაზიარება
Name[lv]=Kopīgot
Name[nl]=Gedeelde
Name[nn]=Del
Name[pl]=Udostępnij
Name[sk]=Zdieľať
Name[sl]=Deli
Name[sv]=Dela
Name[ta]=பகிர்
Name[tr]=Paylaş
Name[uk]=Оприлюднення
Name[x-test]=xxSharexx
Name[zh_TW]=分享
Comment=The result of sharing a piece of content
Comment[ar]=نتيجة مشاركة محتوى
Comment[ca]=El resultat de compartir una peça de contingut
Comment[ca@valencia]=El resultat de compartir una peça de contingut
Comment[en_GB]=The result of sharing a piece of content
Comment[eo]=La rezulto el kundividado de enhavero
Comment[es]=El resultado de compartir una parte de contenido
Comment[eu]=Eduki pieza bat partekatzearen emaitza
Comment[fi]=Tulos yhden sisältöosasen jakamisesta
Comment[fr]=Le résultat du partage d'une partie de contenu.
Comment[hu]=Tartalom megosztásának eredménye
Comment[ia]=Le exito de compartir un pecietta de contento
@@ -288,8 +297,11 @@ Comment[it]=Il risultato della condivisione di un contenuto
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
Comment[lv]=Satura kopīgošanas rezultāts
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[sk]=Výsledok zdieľania obsahu
Comment[sl]=Rezultat deljenega kosa vsebine
Comment[sv]=Resultatet av att dela innehåll
Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவு
Comment[tr]=Bir parça içerik paylaşımının sonucu
Comment[uk]=Результат оприлюднення даних

View File

@@ -136,9 +136,6 @@
<choice name="Socks5">
<label>Socks5</label>
</choice>
<choice name="NoProxy">
<label>NoProxy</label>
</choice>
<default>System</default>
</choices>
</entry>
@@ -182,10 +179,6 @@
<label>Enable secret backup</label>
<default>false</default>
</entry>
<entry name="Phone3PId" type="bool">
<label>Enable add phone numbers as 3PIDs</label>
<default>false</default>
</entry>
</group>
</kcfg>

View File

@@ -69,7 +69,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
setHasFileUploading(false);
});
connect(this, &Room::addedMessages, this, &NeoChatRoom::readMarkerLoadedChanged);
connect(this, &Room::aboutToAddHistoricalMessages, this, &NeoChatRoom::readMarkerLoadedChanged);
const auto &roomLastMessageProvider = RoomLastMessageProvider::self();
@@ -876,7 +876,7 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
qWarning() << "User is not a member of this room so power level cannot be set";
return;
}
int clampPowerLevel = std::clamp(powerLevel, -1, 100);
int clampPowerLevel = std::clamp(powerLevel, 0, 100);
auto powerLevelContent = currentState().get("m.room.power_levels"_ls)->contentJson();
auto powerLevelUserOverrides = powerLevelContent["users"_ls].toObject();
@@ -898,6 +898,254 @@ int NeoChatRoom::getUserPowerLevel(const QString &userId) const
return powerLevelEvent->powerLevelForUser(userId);
}
int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent) const
{
const auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
if (eventName == "ban"_ls) {
return powerLevelEvent->ban();
} else if (eventName == "kick"_ls) {
return powerLevelEvent->kick();
} else if (eventName == "invite"_ls) {
return powerLevelEvent->invite();
} else if (eventName == "redact"_ls) {
return powerLevelEvent->redact();
} else if (eventName == "users_default"_ls) {
return powerLevelEvent->usersDefault();
} else if (eventName == "state_default"_ls) {
return powerLevelEvent->stateDefault();
} else if (eventName == "events_default"_ls) {
return powerLevelEvent->eventsDefault();
} else if (isStateEvent) {
return powerLevelEvent->powerLevelForState(eventName);
} else {
return powerLevelEvent->powerLevelForEvent(eventName);
}
}
void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent)
{
auto powerLevelContent = currentState().get("m.room.power_levels"_ls)->contentJson();
int clampPowerLevel = std::clamp(newPowerLevel, 0, 100);
int powerLevel = 0;
if (powerLevelContent.contains(eventName)) {
powerLevel = powerLevelContent[eventName].toInt();
if (powerLevel != clampPowerLevel) {
powerLevelContent[eventName] = clampPowerLevel;
}
} else {
auto eventPowerLevels = powerLevelContent["events"_ls].toObject();
if (eventPowerLevels.contains(eventName)) {
powerLevel = eventPowerLevels[eventName].toInt();
} else {
if (isStateEvent) {
powerLevel = powerLevelContent["state_default"_ls].toInt();
} else {
powerLevel = powerLevelContent["events_default"_ls].toInt();
}
}
if (powerLevel != clampPowerLevel) {
eventPowerLevels[eventName] = clampPowerLevel;
powerLevelContent["events"_ls] = eventPowerLevels;
}
}
setState("m.room.power_levels"_ls, {}, powerLevelContent);
}
int NeoChatRoom::defaultUserPowerLevel() const
{
return powerLevel("users_default"_ls);
}
void NeoChatRoom::setDefaultUserPowerLevel(const int &newPowerLevel)
{
setPowerLevel("users_default"_ls, newPowerLevel);
}
int NeoChatRoom::invitePowerLevel() const
{
return powerLevel("invite"_ls);
}
void NeoChatRoom::setInvitePowerLevel(const int &newPowerLevel)
{
setPowerLevel("invite"_ls, newPowerLevel);
}
int NeoChatRoom::kickPowerLevel() const
{
return powerLevel("kick"_ls);
}
void NeoChatRoom::setKickPowerLevel(const int &newPowerLevel)
{
setPowerLevel("kick"_ls, newPowerLevel);
}
int NeoChatRoom::banPowerLevel() const
{
return powerLevel("ban"_ls);
}
void NeoChatRoom::setBanPowerLevel(const int &newPowerLevel)
{
setPowerLevel("ban"_ls, newPowerLevel);
}
int NeoChatRoom::redactPowerLevel() const
{
return powerLevel("redact"_ls);
}
void NeoChatRoom::setRedactPowerLevel(const int &newPowerLevel)
{
setPowerLevel("redact"_ls, newPowerLevel);
}
int NeoChatRoom::statePowerLevel() const
{
return powerLevel("state_default"_ls);
}
void NeoChatRoom::setStatePowerLevel(const int &newPowerLevel)
{
setPowerLevel("state_default"_ls, newPowerLevel);
}
int NeoChatRoom::defaultEventPowerLevel() const
{
return powerLevel("events_default"_ls);
}
void NeoChatRoom::setDefaultEventPowerLevel(const int &newPowerLevel)
{
setPowerLevel("events_default"_ls, newPowerLevel);
}
int NeoChatRoom::powerLevelPowerLevel() const
{
return powerLevel("m.room.power_levels"_ls, true);
}
void NeoChatRoom::setPowerLevelPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.power_levels"_ls, newPowerLevel, true);
}
int NeoChatRoom::namePowerLevel() const
{
return powerLevel("m.room.name"_ls, true);
}
void NeoChatRoom::setNamePowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.name"_ls, newPowerLevel, true);
}
int NeoChatRoom::avatarPowerLevel() const
{
return powerLevel("m.room.avatar"_ls, true);
}
void NeoChatRoom::setAvatarPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.avatar"_ls, newPowerLevel, true);
}
int NeoChatRoom::canonicalAliasPowerLevel() const
{
return powerLevel("m.room.canonical_alias"_ls, true);
}
void NeoChatRoom::setCanonicalAliasPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.canonical_alias"_ls, newPowerLevel, true);
}
int NeoChatRoom::topicPowerLevel() const
{
return powerLevel("m.room.topic"_ls, true);
}
void NeoChatRoom::setTopicPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.topic"_ls, newPowerLevel, true);
}
int NeoChatRoom::encryptionPowerLevel() const
{
return powerLevel("m.room.encryption"_ls, true);
}
void NeoChatRoom::setEncryptionPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.encryption"_ls, newPowerLevel, true);
}
int NeoChatRoom::historyVisibilityPowerLevel() const
{
return powerLevel("m.room.history_visibility"_ls, true);
}
void NeoChatRoom::setHistoryVisibilityPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.history_visibility"_ls, newPowerLevel, true);
}
int NeoChatRoom::pinnedEventsPowerLevel() const
{
return powerLevel("m.room.pinned_events"_ls, true);
}
void NeoChatRoom::setPinnedEventsPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.pinned_events"_ls, newPowerLevel, true);
}
int NeoChatRoom::tombstonePowerLevel() const
{
return powerLevel("m.room.tombstone"_ls, true);
}
void NeoChatRoom::setTombstonePowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.tombstone"_ls, newPowerLevel, true);
}
int NeoChatRoom::serverAclPowerLevel() const
{
return powerLevel("m.room.server_acl"_ls, true);
}
void NeoChatRoom::setServerAclPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.server_acl"_ls, newPowerLevel, true);
}
int NeoChatRoom::spaceChildPowerLevel() const
{
return powerLevel("m.space.child"_ls, true);
}
void NeoChatRoom::setSpaceChildPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.space.child"_ls, newPowerLevel, true);
}
int NeoChatRoom::spaceParentPowerLevel() const
{
return powerLevel("m.space.parent"_ls, true);
}
void NeoChatRoom::setSpaceParentPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.space.parent"_ls, newPowerLevel, true);
}
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
{
QStringList events;
@@ -908,11 +1156,7 @@ QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QStri
}
for (const auto &e : events) {
auto job = connection()->callApi<RedactEventJob>(id(), QString::fromLatin1(QUrl::toPercentEncoding(e)), connection()->generateTxnId(), reason);
#if Quotient_VERSION_MINOR > 8
co_await qCoro(job.get(), &BaseJob::finished);
#else
co_await qCoro(job, &BaseJob::finished);
#endif
if (job->error() != BaseJob::Success) {
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
break;

View File

@@ -8,11 +8,13 @@
#include <QCache>
#include <QObject>
#include <QQmlEngine>
#include <QTextCursor>
#include <QCoroTask>
#include <Quotient/user.h>
#include "enums/pushrule.h"
#include "events/pollevent.h"
#include "pollhandler.h"
namespace Quotient
@@ -212,6 +214,101 @@ class NeoChatRoom : public Quotient::Room
*/
Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged)
/**
* @brief The default power level in the room for new users.
*/
Q_PROPERTY(int defaultUserPowerLevel READ defaultUserPowerLevel WRITE setDefaultUserPowerLevel NOTIFY defaultUserPowerLevelChanged)
/**
* @brief The power level required to invite users to the room.
*/
Q_PROPERTY(int invitePowerLevel READ invitePowerLevel WRITE setInvitePowerLevel NOTIFY invitePowerLevelChanged)
/**
* @brief The power level required to kick users from the room.
*/
Q_PROPERTY(int kickPowerLevel READ kickPowerLevel WRITE setKickPowerLevel NOTIFY kickPowerLevelChanged)
/**
* @brief The power level required to ban users from the room.
*/
Q_PROPERTY(int banPowerLevel READ banPowerLevel WRITE setBanPowerLevel NOTIFY banPowerLevelChanged)
/**
* @brief The power level required to delete other user messages.
*/
Q_PROPERTY(int redactPowerLevel READ redactPowerLevel WRITE setRedactPowerLevel NOTIFY redactPowerLevelChanged)
/**
* @brief The default power level for state events that are not explicitly specified.
*/
Q_PROPERTY(int statePowerLevel READ statePowerLevel WRITE setStatePowerLevel NOTIFY statePowerLevelChanged)
/**
* @brief The default power level for event that are not explicitly specified.
*/
Q_PROPERTY(int defaultEventPowerLevel READ defaultEventPowerLevel WRITE setDefaultEventPowerLevel NOTIFY defaultEventPowerLevelChanged)
/**
* @brief The power level required to change power levels for the room.
*/
Q_PROPERTY(int powerLevelPowerLevel READ powerLevelPowerLevel WRITE setPowerLevelPowerLevel NOTIFY powerLevelPowerLevelChanged)
/**
* @brief The power level required to change the room name.
*/
Q_PROPERTY(int namePowerLevel READ namePowerLevel WRITE setNamePowerLevel NOTIFY namePowerLevelChanged)
/**
* @brief The power level required to change the room avatar.
*/
Q_PROPERTY(int avatarPowerLevel READ avatarPowerLevel WRITE setAvatarPowerLevel NOTIFY avatarPowerLevelChanged)
/**
* @brief The power level required to change the room aliases.
*/
Q_PROPERTY(int canonicalAliasPowerLevel READ canonicalAliasPowerLevel WRITE setCanonicalAliasPowerLevel NOTIFY canonicalAliasPowerLevelChanged)
/**
* @brief The power level required to change the room topic.
*/
Q_PROPERTY(int topicPowerLevel READ topicPowerLevel WRITE setTopicPowerLevel NOTIFY topicPowerLevelChanged)
/**
* @brief The power level required to encrypt the room.
*/
Q_PROPERTY(int encryptionPowerLevel READ encryptionPowerLevel WRITE setEncryptionPowerLevel NOTIFY encryptionPowerLevelChanged)
/**
* @brief The power level required to change the room history visibility.
*/
Q_PROPERTY(int historyVisibilityPowerLevel READ historyVisibilityPowerLevel WRITE setHistoryVisibilityPowerLevel NOTIFY historyVisibilityPowerLevelChanged)
/**
* @brief The power level required to pin events in the room.
*/
Q_PROPERTY(int pinnedEventsPowerLevel READ pinnedEventsPowerLevel WRITE setPinnedEventsPowerLevel NOTIFY pinnedEventsPowerLevelChanged)
/**
* @brief The power level required to upgrade the room.
*/
Q_PROPERTY(int tombstonePowerLevel READ tombstonePowerLevel WRITE setTombstonePowerLevel NOTIFY tombstonePowerLevelChanged)
/**
* @brief The power level required to set the room server access control list (ACL).
*/
Q_PROPERTY(int serverAclPowerLevel READ serverAclPowerLevel WRITE setServerAclPowerLevel NOTIFY serverAclPowerLevelChanged)
/**
* @brief The power level required to add children to a space.
*/
Q_PROPERTY(int spaceChildPowerLevel READ spaceChildPowerLevel WRITE setSpaceChildPowerLevel NOTIFY spaceChildPowerLevelChanged)
/**
* @brief The power level required to set the room parent space.
*/
Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged)
/**
* @brief The cache for the main chat bar in the room.
*/
@@ -588,6 +685,66 @@ public:
Q_INVOKABLE void setUserPowerLevel(const QString &userID, const int &powerLevel);
[[nodiscard]] int powerLevel(const QString &eventName, const bool &isStateEvent = false) const;
void setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent = false);
[[nodiscard]] int defaultUserPowerLevel() const;
void setDefaultUserPowerLevel(const int &newPowerLevel);
[[nodiscard]] int invitePowerLevel() const;
void setInvitePowerLevel(const int &newPowerLevel);
[[nodiscard]] int kickPowerLevel() const;
void setKickPowerLevel(const int &newPowerLevel);
[[nodiscard]] int banPowerLevel() const;
void setBanPowerLevel(const int &newPowerLevel);
[[nodiscard]] int redactPowerLevel() const;
void setRedactPowerLevel(const int &newPowerLevel);
[[nodiscard]] int statePowerLevel() const;
void setStatePowerLevel(const int &newPowerLevel);
[[nodiscard]] int defaultEventPowerLevel() const;
void setDefaultEventPowerLevel(const int &newPowerLevel);
[[nodiscard]] int powerLevelPowerLevel() const;
void setPowerLevelPowerLevel(const int &newPowerLevel);
[[nodiscard]] int namePowerLevel() const;
void setNamePowerLevel(const int &newPowerLevel);
[[nodiscard]] int avatarPowerLevel() const;
void setAvatarPowerLevel(const int &newPowerLevel);
[[nodiscard]] int canonicalAliasPowerLevel() const;
void setCanonicalAliasPowerLevel(const int &newPowerLevel);
[[nodiscard]] int topicPowerLevel() const;
void setTopicPowerLevel(const int &newPowerLevel);
[[nodiscard]] int encryptionPowerLevel() const;
void setEncryptionPowerLevel(const int &newPowerLevel);
[[nodiscard]] int historyVisibilityPowerLevel() const;
void setHistoryVisibilityPowerLevel(const int &newPowerLevel);
[[nodiscard]] int pinnedEventsPowerLevel() const;
void setPinnedEventsPowerLevel(const int &newPowerLevel);
[[nodiscard]] int tombstonePowerLevel() const;
void setTombstonePowerLevel(const int &newPowerLevel);
[[nodiscard]] int serverAclPowerLevel() const;
void setServerAclPowerLevel(const int &newPowerLevel);
[[nodiscard]] int spaceChildPowerLevel() const;
void setSpaceChildPowerLevel(const int &newPowerLevel);
[[nodiscard]] int spaceParentPowerLevel() const;
void setSpaceParentPowerLevel(const int &newPowerLevel);
ChatBarCache *mainCache() const;
ChatBarCache *editCache() const;
@@ -703,6 +860,25 @@ Q_SIGNALS:
void defaultUrlPreviewStateChanged();
void urlPreviewEnabledChanged();
void maxRoomVersionChanged();
void defaultUserPowerLevelChanged();
void invitePowerLevelChanged();
void kickPowerLevelChanged();
void banPowerLevelChanged();
void redactPowerLevelChanged();
void statePowerLevelChanged();
void defaultEventPowerLevelChanged();
void powerLevelPowerLevelChanged();
void namePowerLevelChanged();
void avatarPowerLevelChanged();
void canonicalAliasPowerLevelChanged();
void topicPowerLevelChanged();
void encryptionPowerLevelChanged();
void historyVisibilityPowerLevelChanged();
void pinnedEventsPowerLevelChanged();
void tombstonePowerLevelChanged();
void serverAclPowerLevelChanged();
void spaceChildPowerLevelChanged();
void spaceParentPowerLevelChanged();
void replyLoaded(const QString &eventId, const QString &replyId);
public Q_SLOTS:

View File

@@ -72,6 +72,7 @@ Comment[pl]=Znajdź pokoje w NeoChat
Comment[pt]=Procurar salas no NeoChat
Comment[pt_BR]=Encontrar salas no NeoChat
Comment[ru]=Поиск комнат NeoChat
Comment[sk]=Nájsť miestnosti v NeoChat
Comment[sl]=Najdi sobe v NeoChatu
Comment[sv]=Sök efter rum i NeoChat
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்

View File

@@ -4,7 +4,6 @@
#include "proxycontroller.h"
#include <QNetworkProxy>
#include <QNetworkProxyFactory>
#include "neochatconfig.h"
@@ -16,28 +15,18 @@ void ProxyController::setApplicationProxy()
switch (cfg->proxyType()) {
case 1:
proxy.setType(QNetworkProxy::HttpProxy);
proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort());
proxy.setUser(cfg->proxyUser());
proxy.setPassword(cfg->proxyPassword());
QNetworkProxy::setApplicationProxy(proxy);
break;
case 2:
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort());
proxy.setUser(cfg->proxyUser());
proxy.setPassword(cfg->proxyPassword());
QNetworkProxy::setApplicationProxy(proxy);
break;
case 3:
proxy.setType(QNetworkProxy::NoProxy);
QNetworkProxy::setApplicationProxy(proxy);
break;
default:
QNetworkProxyFactory::setUseSystemConfiguration(true);
break;
}
proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort());
proxy.setUser(cfg->proxyUser());
proxy.setPassword(cfg->proxyPassword());
QNetworkProxy::setApplicationProxy(proxy);
}
ProxyController::ProxyController(QObject *parent)

View File

@@ -3,41 +3,21 @@
"Authors": [
{
"Name": "Tobias Fella",
"Name[ca@valencia]": "Tobias Fella",
"Name[ca]": "Tobias Fella",
"Name[cs]": "Tobias Fella",
"Name[de]": "Tobias Fella",
"Name[en_GB]": "Tobias Fella",
"Name[eo]": "Tobias Fella",
"Name[es]": "Tobias Fella",
"Name[eu]": "Tobias Fella",
"Name[fr]": "Tobias Fella",
"Name[gl]": "Tobias Fella",
"Name[hu]": "Tobias Fella",
"Name[ia]": "Tobias Fella",
"Name[it]": "Tobias Fella",
"Name[ka]": "Tobias Fella",
"Name[lv]": "Tobias Fella",
"Name[nl]": "Tobias Fella",
"Name[pl]": "Tobias Fella",
"Name[ru]": "Tobias Fella",
"Name[sl]": "Tobias Fella",
"Name[ar]": "توبياس فلة",
"Name[ta]": "டோபியாஸ் ஃபெல்லா",
"Name[tr]": "Tobias Fella",
"Name[uk]": "Tobias Fella",
"Name[x-test]": "xxTobias Fellaxx",
"Name[zh_TW]": "Tobias Fella"
"Name[x-test]": "xxTobias Fellaxx"
}
],
"Category": "Utilities",
"Description": "Share via NeoChat",
"Description[ar]": "شارك بواسطة نيوتشات",
"Description[ca@valencia]": "Compartix a través de NeoChat",
"Description[ca]": "Comparteix a través del NeoChat",
"Description[de]": "Über NeoChat teilen",
"Description[en_GB]": "Share via NeoChat",
"Description[eo]": "Kundividi per NeoChat",
"Description[es]": "Compartir mediante NeoChat",
"Description[eu]": "Partekatu NeoChat bidez",
"Description[fi]": "Jaa NeoChatillä",
"Description[fr]": "Partager grâce à NeoChat",
"Description[gl]": "Compartir por NeoChat",
"Description[hu]": "Megosztás NeoChatben",
@@ -46,9 +26,10 @@
"Description[ka]": "გააზიარეთ NeoChat-ით",
"Description[lv]": "Kopīgot ar „NeoChat“",
"Description[nl]": "Delen via NeoChat",
"Description[nn]": "Del via NeoChat",
"Description[pl]": "Udostępnij przez NeoChat",
"Description[ru]": "Опубликовать в NeoChat",
"Description[sl]": "Deli prek NeoChat",
"Description[sv]": "Dela via NeoChat",
"Description[ta]": "நியோச்சாட் மூலம் பகிர்",
"Description[tr]": "NeoChat ile Paylaş",
"Description[uk]": "Оприлюднити за допомогою NeoChat",
@@ -57,31 +38,10 @@
"Icon": "org.kde.neochat",
"License": "GPL",
"Name": "NeoChat",
"Name[ast]": "NeoChat",
"Name[ca@valencia]": "NeoChat",
"Name[ca]": "NeoChat",
"Name[cs]": "NeoChat",
"Name[de]": "NeoChat",
"Name[en_GB]": "NeoChat",
"Name[eo]": "NeoChat",
"Name[es]": "NeoChat",
"Name[eu]": "NeoChat",
"Name[fr]": "NeoChat",
"Name[gl]": "NeoChat",
"Name[hu]": "NeoChat",
"Name[ar]": "نيوتشات",
"Name[ia]": "Neochat",
"Name[it]": "NeoChat",
"Name[ka]": "NeoChat",
"Name[lv]": "NeoChat",
"Name[nl]": "NeoChat",
"Name[pl]": "NeoChat",
"Name[ru]": "NeoChat",
"Name[sl]": "NeoChat",
"Name[ta]": "நியோச்சாட்",
"Name[tr]": "NeoChat",
"Name[uk]": "NeoChat",
"Name[x-test]": "xxNeoChatxx",
"Name[zh_TW]": "NeoChat",
"X-Purpose-ActionDisplay": "NeoChat"
},
"X-Purpose-PluginTypes": [

View File

@@ -125,7 +125,7 @@ Kirigami.Dialog {
width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing
}
source: userDelegate.connection.localUser.avatarMediaId ? userDelegate.connection.makeMediaUrl("mxc://" + userDelegate.connection.localUser.avatarMediaId) : ""
source: userDelegate.connection.localUser.avatarMediaId ? ("image://mxc/" + userDelegate.connection.localUser.avatarMediaId) : ""
name: userDelegate.connection.localUser.displayName ?? userDelegate.connection.localUser.id
}

View File

@@ -1,40 +0,0 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.Dialog {
id: root
required property var user
width: Math.min(Kirigami.Units.gridUnit * 24, QQC2.ApplicationWindow.window.width)
height: Kirigami.Units.gridUnit * 8
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
}
customFooterActions: [
Kirigami.Action {
text: i18nc("@action:button", "Start Chat")
icon.name: "im-user"
onTriggered: {
root.user.requestDirectChat();
root.close();
}
}
]
}

View File

@@ -13,13 +13,15 @@ import org.kde.neochat
QQC2.Popup {
id: root
padding: 16
padding: Kirigami.Units.largeSpacing
signal chosen(string path)
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
QQC2.ToolButton {
Layout.preferredWidth: 160
Layout.fillHeight: true
icon.name: 'mail-attachment'
@@ -37,11 +39,8 @@ QQC2.Popup {
Kirigami.Separator {}
QQC2.ToolButton {
Layout.preferredWidth: 160
Layout.fillHeight: true
padding: 16
icon.name: 'insert-image'
text: i18n("Clipboard image")
onClicked: {

View File

@@ -32,7 +32,7 @@ QQC2.ItemDelegate {
visible: root.categoryVisible || filterText.length > 0
contentItem: KirigamiComponents.Avatar {
source: root.avatar ? root.currentRoom.connection.makeMediaUrl("mxc://" + root.avatar) : ""
source: root.avatar ? `image://mxc/${root.avatar}` : ""
name: root.displayName
sourceSize {

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