Compare commits

..

157 Commits

Author SHA1 Message Date
Nicolas Fella
94210a7ce7 Reenable disabled CI builds 2023-03-10 12:49:44 +01:00
l10n daemon script
280d90e191 GIT_SILENT Sync po/docbooks with svn 2023-03-10 02:03:01 +00:00
Nicolas Fella
b4b24430a1 Allow building against KF6 2023-03-09 15:50:43 +01:00
l10n daemon script
b7b7f1817a GIT_SILENT Sync po/docbooks with svn 2023-03-09 01:56:25 +00:00
Joshua Goins
3d67a682f4 Bump kirigami-addons to 0.7.2 2023-03-08 09:33:34 -05:00
Joshua Goins
da6df18367 Add separate "About KDE" page to settings 2023-03-08 14:30:48 +00:00
l10n daemon script
0b1b1b8c8c GIT_SILENT Sync po/docbooks with svn 2023-03-08 02:02:32 +00:00
l10n daemon script
2493a00ba4 GIT_SILENT Sync po/docbooks with svn 2023-03-07 02:01:54 +00:00
Tobias Fella
d6ebb1308c Temporarily disable android Qt6 CI 2023-03-07 00:11:26 +01:00
l10n daemon script
332c311023 GIT_SILENT Sync po/docbooks with svn 2023-03-06 01:56:17 +00:00
Carl Schwan
27d33d121a Small improvement to clipbard
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2023-03-06 00:31:17 +01:00
Tobias Fella
e954fc204f Don't open context menu when tapping on an image
BUG: 466381
2023-03-05 23:19:13 +00:00
Alessio Mattiazzi
d1f7e7091e Copy image to the clipboard
Add a context menu option to save an image in the timeline to the clipboard

BUG: 466478
2023-03-05 23:19:02 +00:00
James Graham
bd4eeb405b Set power level from userdetaildialog
Add the option to set powerlevel to userdetaildialog.

This is done by making the powerleveldialog into it's own file and using that.

implements network/neochat#570
2023-03-05 22:44:04 +00:00
Tobias Fella
e6a060c192 Don't show highlights in DMs
It's a DM, the messages are all meant for us anyway

CCBUG: 466895
2023-03-05 17:21:07 +01:00
l10n daemon script
f53a7a27f6 GIT_SILENT Sync po/docbooks with svn 2023-03-05 02:36:21 +00:00
l10n daemon script
34384dced4 GIT_SILENT made messages (after extraction) 2023-03-05 00:51:47 +00:00
l10n daemon script
b68cfafab2 GIT_SILENT Sync po/docbooks with svn 2023-03-04 01:57:30 +00:00
l10n daemon script
58b836fd1e GIT_SILENT Sync po/docbooks with svn 2023-03-03 01:58:44 +00:00
Alessio Mattiazzi
90cad05bae Fix save-as button in FullScreenImage view
Save-as button in the fullscreenimage view did not work because it could not access the Config object; adding the import statement to access it.
2023-03-02 18:16:08 +00:00
l10n daemon script
072f7cec37 GIT_SILENT Sync po/docbooks with svn 2023-03-02 02:48:28 +00:00
James Graham
39388e204e Roomlist Drag Improvements
- Move the room list drag handler and logic to RoomList.qml.
- Make the compact mode logic not rely on a saved width value from Config as this was causing the room list to have large icons the first launch after a recent update.
- Instead save the compact mode state and initialise the width based upon that.
2023-02-28 19:30:20 +00:00
l10n daemon script
6ccb201110 GIT_SILENT Sync po/docbooks with svn 2023-02-28 02:31:10 +00:00
James Graham
178b516c7c Replace Global Menu
Remove the global menu and move explore and new chat/room buttons to new `ExploreComponent.qml`.

This is designed to sit in the header of the `RoomListPage` and will only be visible when the room list is. The other settings have just been removed as they are covered by the `UserInfo` component and quick can be done form the decoration or system tray.

When wide\
![image](/uploads/ae652b4beeae8cbde0def427fbc65112/image.png)

Collapsed mode\
![image](/uploads/0aac45d8fa1dd718afd764656dded76b/image.png)
2023-02-27 20:11:50 +00:00
Tobias Fella
ac88e13e58 Update Tobias' email address 2023-02-27 19:13:48 +01:00
Kevin Wolf
8d3e145e0b Jump to first unread message with Shift+PgUp
This feature doesn't work very well at the moment, but that's not an
excuse not to have a keyboard shortcut for it. As usual, it's the same
shortcut as many other chat clients use.
2023-02-27 12:05:18 +00:00
Kevin Wolf
f40d1b9f4e Jump to bottom and mark messages read with Escape
Pressing the Escape key again when you're already at the bottom and
there are no unread messages takes you back to the room list like
before.
2023-02-27 12:05:18 +00:00
Kevin Wolf
e9cd165457 Fix Escape shortcut in RoomPage
Since commit eee93e0f1, the ChatBar has a conflicting shortcut that
takes precedence even when it doesn't do anything, so that the Escape
shortcut of RoomPage is never actually invoked any more.

Fix this by using Keys.onShortcutOverride() instead in ChatBar and
accepting the event only when it actually cancelled something. So if
you're writing a reply, the first time pressing Escape cancels the reply
and the second one calls the RoomPage shortcut that goes to the
RoomListPage.
2023-02-27 12:05:18 +00:00
Kevin Wolf
c0c86c67b6 Alt+Shift+Up/Down to switch to previous/next unread room
This is the same key combination as commonly used by other chat clients
such as Element, Slack and Discord. For consistency, also add the same
Alt+Up/Down shortcut as used in thes other clients as aliases for
switching rooms without considering the unread status.
2023-02-27 12:05:18 +00:00
Kevin Wolf
12afa43d23 Scroll the room content with PgUp/PgDown
This is pretty much standard behaviour in any chat client. We should do
the same.
2023-02-27 12:05:18 +00:00
Kevin Wolf
386f637b94 Add Ctrl+U shortcut for switching user
This is a key combination that doesn't seem to be used either in NeoChat
nor with a common meaning in other chat clients, so let's use it for
switching the user.
2023-02-27 12:05:18 +00:00
Kevin Wolf
d7bd9f4609 Use Shortcut for keyboard room switching
This allows to use the room switching shortcuts like Ctrl+PgUp/PgDn even
when the RoomPage doesn't currently have the focus. It's also a nice
code simplification.
2023-02-27 12:05:18 +00:00
Kevin Wolf
33c9edc9a3 Don't switch to invisible first/last room on Ctrl+PgUp/PgDn
If you're on the first visible room and try to switch to the previous
one (or on the last visible room switching to the next one), the first
(or last) room in the list is incorrectly selected even if it's not
currently visible.

Fix this by not first entering each room and then checking whether it is
the one we really wanted, but iterating until we find a room that we
want and only entering it then.
2023-02-27 12:05:18 +00:00
Kevin Wolf
3b2dbc731e RoomListPage: Fix skipping invisible items in goToNextRoom()
We don't want to skip over invisible items if they are one element after
the end of the ListView, but if they aren't the last item.
2023-02-27 12:05:18 +00:00
l10n daemon script
51a29ac528 GIT_SILENT Sync po/docbooks with svn 2023-02-27 02:37:04 +00:00
James Graham
cc8bf79a9b Allow read marker to be cleared when at 0 in Timeline
The read marker can be at posiiton 0 in the timeline so make sure that can be cleared by checking >=0 in markReadIfVisible
2023-02-26 09:20:15 +00:00
l10n daemon script
8d47e58861 GIT_SILENT Sync po/docbooks with svn 2023-02-26 02:23:15 +00:00
James Graham
47ce8a4846 Add button to copy the room Id to clipboard 2023-02-25 22:09:13 +00:00
James Graham
71c9537c61 Fix Inline Edit Focus
Make sure that the inline edit component get focus when an edit is started and the cursor is at the end
2023-02-25 16:30:17 +00:00
Nicolas Fella
8825e6ec83 Add parent windows to file dialogs
This makes sure they are properly placed and modal to the main window
2023-02-25 12:14:58 +00:00
James Graham
44ec93f0a0 Change plain text for a spolier from /rainbow to /spoiler. 2023-02-25 10:34:53 +00:00
l10n daemon script
ed7688e66f GIT_SILENT Sync po/docbooks with svn 2023-02-25 03:06:51 +00:00
Tobias Fella
6b49854b12 Automatically open room after creation 2023-02-24 18:15:50 +00:00
l10n daemon script
e8484ebc7a GIT_SILENT Sync po/docbooks with svn 2023-02-24 02:19:24 +00:00
Tobias Fella
17d01c68c4 Prefer password login over sso login when failing to query flows
Password login is more likely to be correct, maybe this can help in some cases

Fixes #513
2023-02-23 20:06:30 +00:00
Joshua Goins
dae2cbab90 Refresh the notification count when unread stats change
libquotient doesn't update the notification count properly, so let's
listen on the unread stats changing instead.
2023-02-23 19:36:13 +00:00
l10n daemon script
b8e8fa3ee5 GIT_SILENT Sync po/docbooks with svn 2023-02-23 02:21:10 +00:00
Tobias Fella
9c4a925171 Indicate messages that are not sent yet 2023-02-22 23:21:03 +00:00
Tobias Fella
8996806b05 Copy other user's matrix ID instead of room id for direct messages
Implements #469
2023-02-22 19:56:37 +00:00
l10n daemon script
2bd4579c10 GIT_SILENT Sync po/docbooks with svn 2023-02-22 02:11:15 +00:00
l10n daemon script
0891f32c08 GIT_SILENT Sync po/docbooks with svn 2023-02-21 02:19:57 +00:00
Tobias Fella
5287c2d529 Focus member search field when opening it 2023-02-20 23:27:05 +01:00
Kevin Wolf
084b89f3dc Fix two null pointer dereferences 2023-02-20 19:00:54 +00:00
James Graham
2dd3197beb Inline Edit Fixes
- Make sure the connection to the room's chatBoxEditIdChanged signal is made so that the edit box gets filled.
- Make sure the minimum height and preferred width are available to the loader so they can be set and dynamically updated.

BUG: 465934
2023-02-20 18:24:00 +00:00
Tobias Fella
6d9dca7da8 Show a warning when a file is too large to download
BUG: 465527
2023-02-20 17:19:19 +00:00
Kevin Wolf
e0f16054fc Fix Ctrl+PgUp/PgDn shortcut for room switching
Before this change, Ctrl+PgUp/PgDown didn't select the next room, but
actually moved two rooms at once because the signal handler was called
twice. I'm not entirely sure why this happens (it might be a bug in a
lower layer?), but as we don't want to handle the same event elsewhere,
accepting the event doesn't feel wrong and it appears to fix the issue.
So let's just do that.

BUG: 459997
2023-02-20 17:07:35 +00:00
l10n daemon script
fa70679439 GIT_SILENT Sync po/docbooks with svn 2023-02-20 02:21:40 +00:00
l10n daemon script
949bd20873 GIT_SILENT Sync po/docbooks with svn 2023-02-19 02:24:50 +00:00
l10n daemon script
79a9eb0de0 GIT_SILENT Sync po/docbooks with svn 2023-02-18 02:21:24 +00:00
Shooting Star
e1b9bc7d0e Add knock command 2023-02-17 13:29:02 +00:00
l10n daemon script
a1abf22174 GIT_SILENT Sync po/docbooks with svn 2023-02-17 02:15:57 +00:00
Tobias Fella
fa27d993e2 Don't complete emojis when typing uppercase letters
BUG: 463049
2023-02-16 15:06:52 +00:00
l10n daemon script
d4b750433e GIT_SILENT Sync po/docbooks with svn 2023-02-16 03:09:51 +00:00
Tobias Fella
9df534c72c Check if connection is null when handling notifications
BUG: 465143
2023-02-16 00:30:27 +01:00
James Graham
f785e4d5b0 Inline Edit Loader
Put the edit component in a loader to that it doesn't use resources when not in use.

CCBUG: 465715
2023-02-15 19:30:23 +00:00
James Graham
f186be7314 Disable link loading animation when offscreen
Stop the link preview loading indicator animation running when the delegate isn't visible.

CCBUG: 465715
2023-02-15 18:22:51 +00:00
l10n daemon script
3cbcc2b597 GIT_SILENT Sync po/docbooks with svn 2023-02-15 02:19:47 +00:00
l10n daemon script
61f3d6c9ae GIT_SILENT Sync po/docbooks with svn 2023-02-14 02:18:22 +00:00
l10n daemon script
d8b2436f0d GIT_SILENT Sync po/docbooks with svn 2023-02-13 02:14:05 +00:00
James Graham
ea76edce74 Improve Link Preview Sizing
Update linkpreviewer to minimise the number of resizes.

- There is now a placeholder loading element to hold space while the data is obtained
- The component starts with a set size but can be expanded to see the whole item.
- The expand/contract button is only visible on hover
- The default size is picked to show 3 lines of text (1 heading and 2 description, 2 heading and 1 description, etc)

This is designed to help with the timeline scroll jumping CCBUG: 463235 by minimizing the amount of resizing for items in the timeline.

Before

![image](/uploads/bb46f55dc38851e640a7382aafea8640/image.png)

After when compressed

![image](/uploads/3cbc7894688721d944a5551251b12a12/image.png)

After When expanded

![image](/uploads/50ff53fe51eefe88713a2ded34530534/image.png)

After Loading

![image](/uploads/72567f805af6b08ab0c5fc1cf94bd247/image.png)
2023-02-12 21:10:52 +00:00
James Graham
5482aad7ba Inline Edits
Edit text messages inline instead of in the chatbar
2023-02-12 13:46:23 +00:00
James Graham
aaa26571d1 Foldable State Events
Implement folding sections for multiple consecutive state events. This also reworks some of the aggregated text output:

- Only new days cause a new section
- The aggregated text starts with either a single username or n users
- If the same user did the same action mutltiple times it will be in the aggregated text a user did x n times
- When there are multiple authors in a block with multiple state event types it will use or rather than and before the last event. e.g 3 user left the room or joined the room.

Folded

![image](/uploads/17141ed5e1f2e0e3539523a082275976/image.png)

Unfolded

![image](/uploads/1d5db5804da457b921544cc81099997d/image.png)

Implements network/neochat#90
2023-02-12 11:43:39 +00:00
l10n daemon script
554e3576fd GIT_SILENT Sync po/docbooks with svn 2023-02-11 02:13:55 +00:00
l10n daemon script
8d2c7ff8c2 GIT_SILENT Sync po/docbooks with svn 2023-02-10 02:19:19 +00:00
l10n daemon script
92261eb94f GIT_SILENT Sync po/docbooks with svn 2023-02-09 02:31:12 +00:00
Tobias Fella
4dfb7fc3e2 Use 22.08 flatpak runtime for CI 2023-02-08 18:19:22 +01:00
l10n daemon script
2728446692 GIT_SILENT Sync po/docbooks with svn 2023-02-08 03:07:40 +00:00
Devin Lin
dfb32fe687 Fix project name 2023-02-07 20:15:48 +00:00
Devin Lin
55507112a9 Move neochat to the release service 2023-02-07 20:15:48 +00:00
Nikita Karpei
d9aa77a9f4 Fix crash after logout 2023-02-07 19:54:27 +00:00
Nikita Karpei
c0ea52c331 Fix crash after login 2023-02-07 18:53:49 +00:00
Nikita Karpei
f1b9d2329f Don't play GIF images while Neochat isn't focused
BUG: 465019
2023-02-07 17:33:10 +00:00
l10n daemon script
0707fd8189 GIT_SILENT Sync po/docbooks with svn 2023-02-07 02:21:31 +00:00
Nikita Karpei
6bef2205db Delete a device from the list if the job succeed 2023-02-06 20:51:26 +00:00
Wang Zichong
ace62b4df1 Use SelectableLabel for selectable labels 2023-02-06 11:16:08 +08:00
l10n daemon script
d76e512785 GIT_SILENT Sync po/docbooks with svn 2023-02-06 02:25:46 +00:00
l10n daemon script
db2692c411 GIT_SILENT Sync po/docbooks with svn 2023-02-05 02:15:49 +00:00
l10n daemon script
4c9bdd03ce GIT_SILENT Sync po/docbooks with svn 2023-02-04 02:37:23 +00:00
l10n daemon script
efa2c012a7 GIT_SILENT Sync po/docbooks with svn 2023-02-03 02:27:55 +00:00
Shooting Star
072dc1b6a3 Show error if room join failed 2023-02-02 21:31:15 +00:00
James Graham
4066427168 Fix Account Switcher Missing Clicks
Use a state machine to open and close the account switcher so clicks aren't missed.

BUG: 462549
2023-02-02 21:19:04 +00:00
James Graham
b901c1bdf2 Pull in the updates from video delegate to image delegate to improve sizing behaviour. 2023-02-02 19:28:49 +00:00
l10n daemon script
532310d739 GIT_SILENT Sync po/docbooks with svn 2023-02-02 02:22:20 +00:00
Tobias Fella
4f7d32df2b Fix compilation against Qt6 and add CI 2023-02-01 16:34:39 +00:00
l10n daemon script
833d2159e7 GIT_SILENT Sync po/docbooks with svn 2023-02-01 02:14:48 +00:00
l10n daemon script
ccf8701395 GIT_SILENT Sync po/docbooks with svn 2023-01-31 02:17:09 +00:00
l10n daemon script
5d696aabc4 GIT_SILENT Sync po/docbooks with svn 2023-01-30 02:33:24 +00:00
Tobias Fella
ebd521e2ee Don't crash when leaving an opened room
Should fix #568
2023-01-29 13:51:52 +00:00
Tobias Fella
9d5303113e Close login window after the account is loaded 2023-01-29 09:26:09 +00:00
l10n daemon script
3850fe15a5 GIT_SILENT Sync po/docbooks with svn 2023-01-29 02:21:51 +00:00
Tobias Fella
5392481d06 Try fixing sqlite on android 2023-01-28 23:50:14 +01:00
Tobias Fella
0ce048517b Update gitignore 2023-01-28 22:57:15 +01:00
James Graham
c51e16e16c Add basic video controls
Add play, volume and duration slider

![image](/uploads/e85c6685c3d81c7a3839727a995b816f/image.png)
2023-01-28 19:17:17 +00:00
Joshua Goins
765a95050d Replace deprecated usage of iconName with icon.name 2023-01-28 17:48:45 +00:00
l10n daemon script
38dcefffcb GIT_SILENT Sync po/docbooks with svn 2023-01-28 02:22:22 +00:00
Carl Schwan
391b61dcb4 Bump cmake version to 23.01
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
2023-01-27 20:47:44 +00:00
Tobias Fella
d76c9fcbcf Add release notes for 23.01 2023-01-27 20:47:44 +00:00
Tobias Fella
f7b8ae2af2 Link against sqlite on android
This is needed to ensure that the sqlite lib is packaged into the apk
2023-01-27 21:33:57 +01:00
l10n daemon script
ddbf9b60d4 GIT_SILENT Sync po/docbooks with svn 2023-01-27 02:17:55 +00:00
James Graham
8948ff5faa Add padding to the right of the timeline delegate highlight same as left 2023-01-26 15:50:52 +00:00
l10n daemon script
d270e202a8 GIT_SILENT Sync po/docbooks with svn 2023-01-26 02:09:20 +00:00
l10n daemon script
61e1dd14ba GIT_SILENT Sync po/docbooks with svn 2023-01-25 02:22:18 +00:00
James Graham
eee93e0f1f Chatbar Refactor
This is mostly a stylistic rework of the chatbar but there are some buxfixes / improvements in here as well. The aim was to make the chatbar so it's size was managed in the same manner as the timeline in both bubble and compactmode in the same manner as network/neochat!476.

The other features are:
- Replies, attachments and edits now look like they are inside the chatbar and use a similar styling to edits in message bubbles
- Replies and edits now part of the message so they arte part of the ScrollView and will scroll away when the text is long
- ~~The emoji picker is now a popup so it doesn't mess with the timeline layout when activated~~ (done in network/neochat!697)
- ~~Emoji dialog is now no longer required as the picker itself is a popup now~~ (no longer the case see above)
- The scrollbar now sits on the right of the chatbar actions rather than weirdly to the left
- The action icons will always stay in the same place even as the chatbar gets taller


Updated\
![2022-12-10_14-13-41.mkv](/uploads/ccbe14843834a19bf98ef0028e023eae/2022-12-10_14-13-41.mkv)

Scrollbar behaviour before
![chatbar_refactor_scrollbar_before](/uploads/2f3b91a79eb302ccf83dd35e51004e6a/chatbar_refactor_scrollbar_before.png)

Scrollbar behaviour after
![chatbar_refactor_scrollbar_after](/uploads/fcab044d8a4338ed9bcff6721b65e89c/chatbar_refactor_scrollbar_after.png)
2023-01-24 18:02:19 +00:00
l10n daemon script
826760a55c GIT_SILENT Sync po/docbooks with svn 2023-01-24 02:18:50 +00:00
Tamara Schmitz
749398df8f remove execute bit from LICENSE file 2023-01-23 11:29:01 +01:00
l10n daemon script
150968d226 GIT_SILENT Sync po/docbooks with svn 2023-01-23 02:10:28 +00:00
l10n daemon script
d81df24e7c 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"
2023-01-23 01:56:42 +00:00
James Graham
594a5cf6ca Move the qt models to their own folder
Felt like the src folder was getting a bit crowded so move all the models to a folder named models.
2023-01-22 21:33:30 +00:00
Joshua Goins
0af420b824 Improve link regex to fix handling paranthesis and question marks
This patch fixes two key issues:
* `?` is considered part of the URL, even if there might not be anything past it. I've seen people now putting spaces after URLs to get around this issue.
* `)` is also part of the URL, as well as `:` despite those not being valid characters at the end of URLs.
2023-01-22 18:35:31 +00:00
l10n daemon script
31fed8362a GIT_SILENT Sync po/docbooks with svn 2023-01-22 02:19:48 +00:00
Joshua Goins
dbcf8c6327 Change placeholder text to make it clear that it's going to be a caption
When sending an image as an attachment, the message you type into the
chat box becomes its caption. However, it's not clear when in the chat
box itself, which confusingly says you're typing a message.
2023-01-21 22:10:50 +00:00
l10n daemon script
8012392400 GIT_SILENT Sync po/docbooks with svn 2023-01-21 02:13:08 +00:00
l10n daemon script
3e48578b44 GIT_SILENT Sync po/docbooks with svn 2023-01-20 02:15:42 +00:00
Nicolas Fella
d7e656e57f Activate window first thing when clicking on notification
Currently we enter the room first, which introduces a noticeable delay between clicking the notification and the window being activated
2023-01-19 17:56:26 +00:00
Volker Krause
8e83b923d9 Adapt APK build to Qt 5.15.8
This should fix the binary factory build, but it unfortunately not backward
compatible below Qt 5.15.8. Qt 6 will need the same changes anyway, so it's
not all bad at least.
2023-01-19 17:17:47 +01:00
l10n daemon script
a739f0f09f GIT_SILENT Sync po/docbooks with svn 2023-01-19 02:26:37 +00:00
Gary Wang
bc74737b0f Avoid logout when loginError caused by network error 2023-01-18 12:01:10 +08:00
l10n daemon script
e92fccf904 GIT_SILENT Sync po/docbooks with svn 2023-01-18 02:12:14 +00:00
l10n daemon script
20cbedfff5 GIT_SILENT Sync po/docbooks with svn 2023-01-17 02:30:38 +00:00
Xaver Hugl
e0d508d3dd hide the time header in chats if it would be transparent
Text over other text without a background is unreadable and looks horrible,
it's better to not have the header at all
2023-01-16 21:18:17 +01:00
l10n daemon script
af318a2bae GIT_SILENT Sync po/docbooks with svn 2023-01-16 02:19:17 +00:00
l10n daemon script
6b4e81c763 GIT_SILENT Sync po/docbooks with svn 2023-01-15 02:07:37 +00:00
l10n daemon script
dcac63aa04 GIT_SILENT Sync po/docbooks with svn 2023-01-14 17:44:16 +00:00
Nicolas Fella
7818747e45 Also handle notification startupId on X11
On X11 we can get a startupId/token from the notification too, so don't waste it
2023-01-14 12:51:15 +00:00
l10n daemon script
7cb1f856ca GIT_SILENT Sync po/docbooks with svn 2023-01-14 12:42:44 +00:00
James Graham
5955c8e7dc Improve global notification settings
Separate the setting for globally setting push rules on/off from the configuration to decide whether desktop popup notifications are sent.

The current master setting is pulling double duty and should probably be separate as some people may want to see notification counts in Neochat but don't want to see popup notifications on their desktop.
2023-01-14 12:29:08 +00:00
Gary Wang
dee3c279e8 fix crash when open invitation page 2023-01-14 06:17:32 +00:00
l10n daemon script
94427280d8 GIT_SILENT Sync po/docbooks with svn 2023-01-14 02:15:51 +00:00
Gary Wang
8b245b1cc9 fix crash on invite via /invite command 2023-01-13 22:15:22 +08:00
l10n daemon script
0513cd10c4 GIT_SILENT Sync po/docbooks with svn 2023-01-13 02:56:44 +00:00
James Graham
28b5631d06 Update userlistmodel permission roles
Change the userlist model so that permission names are based upon the following power level convention:
- 0: member
- 50: moderator
- 100: admin
- other: custom

This is inline with the new permission room page. Follow on from network/neochat!712
2023-01-12 19:13:20 +00:00
l10n daemon script
5f2cd92da7 GIT_SILENT Sync po/docbooks with svn 2023-01-12 02:16:04 +00:00
Tobias Fella
4535125c54 Change message shown when encryption is disabled in libQuotient 2023-01-11 19:03:29 +00:00
James Graham
8831da956a Notifications for all accounts
- Handle notifications from all accounts not just the active one.
- When a notification from the non-active account is clicked the active account is changed over to enter the correct room.

network/neochat#121
2023-01-11 19:02:51 +00:00
Akseli Lahtinen
f2ec6e1d4c Write inside .cache folder in home, instead of just home
Back when testing !745, i was using `kdesrc-run` and this bug slipped through my fingers: It began to create folder named after the file inside the home (or in this case, kdesrc-run home) folder.

This fixes that. Now the clipboard image files go correctly to `~/.cache/KDE/screenshots/` folder.

Anyhow, my bad, should've done more print debugging.. 😓
2023-01-11 12:58:23 +00:00
l10n daemon script
93f7def532 GIT_SILENT Sync po/docbooks with svn 2023-01-11 02:17:17 +00:00
Wang Zichong
782f5517d3 Fix falsely treat some symbols as emoji 2023-01-10 22:20:49 +00:00
Brent Mackey
f238c18ce8 Fix last message read receipt reporting time in GMT instead of localtime 2023-01-10 20:59:31 +10:00
l10n daemon script
4fe0ea373f GIT_SILENT Sync po/docbooks with svn 2023-01-10 02:13:38 +00:00
l10n daemon script
982d21dd58 GIT_SILENT Sync po/docbooks with svn 2023-01-09 02:16:57 +00:00
l10n daemon script
7002132bde GIT_SILENT Sync po/docbooks with svn 2023-01-08 02:18:44 +00:00
l10n daemon script
1ef931f7e7 GIT_SILENT Sync po/docbooks with svn 2023-01-07 02:50:39 +00:00
l10n daemon script
8797015c6a GIT_SILENT Sync po/docbooks with svn 2023-01-06 02:39:53 +00:00
Gary Wang
85a562d469 Move encrypt room option to Security page 2023-01-05 16:13:59 +00:00
Akseli Lahtinen
f50c62ba12 Use local file path instead of url
If the localFile url is used, it creates a folder called `file:` in the home folder.

So get filepath from url instead :)
2023-01-05 16:09:20 +00:00
Gary Wang
13f05a0995 Move invite option to header of members section
This patch moves the invite option to header of members section. We also check if user can send invitation event and we won't show the invite button if it's not allowed.

This patch also added an toggle button to show the member search bar since it won't needed by the user in most of the cases.
2023-01-05 14:56:29 +00:00
l10n daemon script
1adddcc0d9 GIT_SILENT Sync po/docbooks with svn 2023-01-05 02:21:03 +00:00
203 changed files with 52624 additions and 32345 deletions

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "5.15-21.08",
"runtime-version": "5.15-22.08",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@ compile_commands.json
kate.project.ctags.*
*.user
.flatpak-builder/
.idea/

View File

@@ -4,9 +4,11 @@
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
# TODO enable once we can have qt6 libQuotient on the CI
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml

View File

@@ -1,8 +1,8 @@
# SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
# SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
Dependencies:
- 'on': ['@all']
- 'on': ['Linux/Qt5', 'Android/Qt5', 'FreeBSD/Qt5', 'Windows/Qt5']
'require':
'frameworks/extra-cmake-modules': '@stable'
'frameworks/kcoreaddons': '@stable'
@@ -19,15 +19,42 @@ Dependencies:
'third-party/qtkeychain': '@latest'
'third-party/cmark': '@latest'
'third-party/qcoro': '@latest'
- 'on': ['Windows', 'Linux', 'FreeBSD']
- 'on': ['Windows/Qt5', 'Linux/Qt5', 'FreeBSD/Qt5']
'require':
'frameworks/qqc2-desktop-style': '@stable'
'frameworks/kio': '@stable'
'frameworks/kwindowsystem': '@stable'
'frameworks/kconfigwidgets': '@stable'
- 'on': ['Linux', 'FreeBSD']
- 'on': ['Linux/Qt5', 'FreeBSD/Qt5']
'require':
'frameworks/kdbusaddons': '@stable'
- 'on': ['Linux/Qt6', 'Android/Qt6', 'FreeBSD/Qt6', 'Windows/Qt6']
'require':
'frameworks/extra-cmake-modules': '@latest-kf6'
'frameworks/kcoreaddons': '@latest-kf6'
'frameworks/kirigami': '@latest-kf6'
'frameworks/ki18n': '@latest-kf6'
'frameworks/kconfig': '@latest-kf6'
'frameworks/syntax-highlighting': '@latest-kf6'
'frameworks/kitemmodels': '@latest-kf6'
'frameworks/knotifications': '@latest-kf6'
'libraries/kquickimageeditor': '@latest-kf6'
'frameworks/sonnet': '@latest-kf6'
'libraries/kirigami-addons': '@latest-kf6'
'third-party/libquotient': '@latest'
'third-party/qtkeychain': '@latest'
'third-party/cmark': '@latest'
'third-party/qcoro': '@latest'
- 'on': ['Windows/Qt6', 'Linux/Qt6', 'FreeBSD/Qt6']
'require':
'frameworks/qqc2-desktop-style': '@latest-kf6'
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kconfigwidgets': '@latest-kf6'
- 'on': ['Linux/Qt6', 'FreeBSD/Qt6']
'require':
'frameworks/kdbusaddons': '@latest-kf6'
Options:
require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD', 'Windows' ]

View File

@@ -7,7 +7,7 @@ Copyright: 2020 Carson Black <uhhadd@gmail.com>
License: LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
Files: android/res/drawable/splash.xml
Copyright: 2020 Tobias Fella <fella@posteo.de>
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: .gitignore
@@ -27,11 +27,11 @@ Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: BSD-2-Clause
Files: src/neochatconfig.kcfg
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <fella@posteo.de>
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: src/neochat.notifyrc
Copyright: 2020 Tobias Fella <fella@posteo.de>
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: src/qml/Component/confetti.png src/qml/Component/glowdot.png
@@ -39,5 +39,5 @@ Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
License: CC0-1.0
Files: .flatpak-manifest.json
Copyright: 2020-2022 Tobias Fella <fella@posteo.de>
Copyright: 2020-2022 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause

View File

@@ -1,18 +1,26 @@
# SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carl@carlschwan.eu>
# SPDX-FileCopyrightText: 2020-2021 Nicolas Fella <nicolas.fella@gmx.de>
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
cmake_minimum_required(VERSION 3.16)
project(NeoChat)
set(PROJECT_VERSION "22.11")
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "23")
set(RELEASE_SERVICE_VERSION_MINOR "03")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
set(KF5_MIN_VERSION "5.91.0")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
set(KF_MIN_VERSION "5.91.0")
set(QT_MIN_VERSION "5.15.2")
if (ANDROID)
set(QT_MIN_VERSION "5.15.8")
endif()
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
@@ -49,16 +57,16 @@ set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet ItemModels)
set_package_properties(KF5 PROPERTIES
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet ItemModels)
set_package_properties(KF${QT_MAJOR_VERSION} PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
set_package_properties(KF5Kirigami2 PROPERTIES
TYPE REQUIRED
PURPOSE "Kirigami application UI framework"
)
find_package(KF5KirigamiAddons 0.6 REQUIRED)
set_package_properties(KF${QT_MAJOR_VERSION}Kirigami2 PROPERTIES
TYPE REQUIRED
PURPOSE "Kirigami application UI framework"
)
find_package(KF${QT_MAJOR_VERSION}KirigamiAddons 0.7.2 REQUIRED)
find_package(Qt${QT_MAJOR_VERSION}Keychain)
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
@@ -74,15 +82,15 @@ if(ANDROID)
)
else()
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem)
set_package_properties(KF${QT_MAJOR_VERSION}QQC2DesktopStyle PROPERTIES
TYPE RUNTIME
)
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
find_package(KF5DBusAddons ${KF5_MIN_VERSION} REQUIRED)
find_package(KF${QT_MAJOR_VERSION}DBusAddons ${KF_MIN_VERSION} REQUIRED)
endif()
find_package(Quotient 0.6)
@@ -116,12 +124,14 @@ find_package(QCoro${QT_MAJOR_VERSION} 0.4 COMPONENTS Core REQUIRED)
qcoro_enable_coroutines()
find_package(KF5DocTools ${KF5_MIN_VERSION})
set_package_properties(KF5DocTools PROPERTIES DESCRIPTION
find_package(KF${QT_MAJOR_VERSION}DocTools ${KF_MIN_VERSION})
set_package_properties(KF${QT_MAJOR_VERSION}DocTools PROPERTIES DESCRIPTION
"Tools to generate documentation"
TYPE OPTIONAL
)
find_package(Sqlite3)
if(NOT Quotient_VERSION_MINOR GREATER 6)
cmake_policy(SET CMP0063 OLD)
endif()
@@ -145,7 +155,7 @@ if (BUILD_TESTING AND Quotient_VERSION_MINOR GREATER 6)
add_subdirectory(autotests)
endif()
if(KF5DocTools_FOUND)
if(KF${QT_MAJOR_VERSION}DocTools_FOUND)
kdoctools_install(po)
add_subdirectory(doc)
endif()

0
LICENSES/MIT.txt Executable file → Normal file
View File

View File

@@ -1,6 +1,6 @@
<!--
SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
SPDX-License-Identifier: CC0-1.0
-->
# NeoChat

View File

@@ -14,7 +14,8 @@
android:name="org.qtproject.qt5.android.bindings.QtActivity"
android:label="NeoChat"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTop">
android:launchMode="singleTop"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -22,7 +23,6 @@
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="neochat-app"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
@@ -38,8 +38,6 @@
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<!-- Splash screen -->

View File

@@ -12,7 +12,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.4'
classpath 'com.android.tools.build:gradle:7.0.2'
}
}
@@ -73,6 +73,10 @@ android {
defaultConfig {
minSdkVersion qtMinSdkVersion
targetSdkVersion qtTargetSdkVersion
applicationId "org.kde.neochat"
namespace "org.kde.neochat"
versionCode timestamp
versionName projectVersionFull
manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
}

View File

@@ -2,7 +2,7 @@
<!--
- SPDX-License-Identifier: CC0-1.0
- SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
- SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
- SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
-->
<component type="desktop">
<id>org.kde.neochat</id>
@@ -179,7 +179,7 @@
<category>Network</category>
</categories>
<developer_name>The KDE Community</developer_name>
<developer_name xml:lang="ar">مجتمع كدي</developer_name>
<developer_name xml:lang="ar">مجتمع كِيدِي</developer_name>
<developer_name xml:lang="az">KDE Cəmiyyəti</developer_name>
<developer_name xml:lang="ca">La comunitat KDE</developer_name>
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
@@ -231,6 +231,20 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="23.01" date="2023-01-30">
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
<description>
<p>New features and bugfixes:</p>
<ul>
<li>Notifications will now be shown for all accounts, not just the active one</li>
<li>There is a new "compact" mode for the room list</li>
<li>You can now search in the room history</li>
<li>Emojis and Reactions have been significantly improved</li>
<li>Fixed several crashes around user invitations</li>
<li>Room permission settings can now be configured</li>
</ul>
</description>
</release>
<release version="22.11" date="2022-11-30">
<url>https://plasma-mobile.org/2022/11/30/plasma-mobile-gear-22-11/</url>
</release>

View File

@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
# SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
# SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
[Desktop Entry]
Version=1.5
Name=NeoChat

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

3743
po/lt/neochat.po Normal file

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

@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
#
# SPDX-License-Identifier: GPL-3.0-or-later
kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR})

View File

@@ -0,0 +1,122 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
<!ENTITY % Turkish "INCLUDE">
]>
<!--
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<refentry lang="&language;">
<refentryinfo>
<title
>NeoChat Kullanıcı Kılavuzu</title>
<author
><firstname
>Carl</firstname
><surname
>Schwan</surname
> <contrib
>NeoChat man page.</contrib
> <email
>carl@carlschwan.eu</email
></author>
<date
>2022-11-01</date>
<releaseinfo
>22.09</releaseinfo>
<productname
>NeoChat</productname>
</refentryinfo>
<refmeta>
<refentrytitle>
<command
>neochat</command>
</refentrytitle>
<manvolnum
>1</manvolnum>
</refmeta>
<refnamediv>
<refname
>neochat</refname>
<refpurpose
>Matrix iletileşme protokolü ile etkileşim için istemci</refpurpose>
</refnamediv>
<!-- body begins here -->
<refsynopsisdiv id='synopsis'>
<cmdsynopsis
><command
>neochat</command
> <arg choice="opt"
><replaceable
>URI</replaceable
></arg
> </cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
<title
>Açıklama</title>
<para
><command
>neochat</command
>, Matrix protokolü için hem masaüstünde hem de taşınabilir aygıtlarda çalışan bir sohbet uygulamasıdır. </para>
</refsect1>
<refsect1 id="options"
><title
>Seçenekler</title>
<variablelist>
<varlistentry>
<term
><option
>URI</option
></term>
<listitem>
<para
>Bir kullanıcı veya oda için matrix URI'si; örneğin, matrix:u/kullanıcı:örnek.org ve matrix:r/kök:örnek.org. Bu, NeoChat'in verilen odayı veya konuşmayı açmayı denemesini sağlar. </para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="bug">
<title
>Hata Bildirme</title>
<para
>Hataları veya özellik isteklerini <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General"
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General</ulink
> bağlantısından bildirebilirsiniz</para>
</refsect1>
<refsect1>
<title
>Ayrıca</title>
<simplelist>
<member
>Matrix üzerine sıkça sorulan soruların bir listesi: <ulink url="https://matrix.org/faq/"
>https://matrix.org/faq/</ulink
> </member>
<member
>kf5options(7)</member>
<member
>qt5options(7)</member>
</simplelist>
</refsect1>
<refsect1 id="copyright"
><title
>Telif Hakkı</title>
<para
>Telif hakkı &copy; 2020-2022 Tobias Fella </para>
<para
>Telif hakkı &copy; 2020-2022 Carl Schwan </para>
<para
>Lisans: GNU Genel Kamu Lisansa, 3. sürüm veya sonrası &lt;<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
>&gt;</para>
</refsect1>
</refentry>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +1,52 @@
# SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carl@carlschwan.eu>
# SPDX-FileCopyrightText: 2020-2021 Nicolas Fella <nicolas.fella@gmx.de>
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
add_library(neochat STATIC
controller.cpp
actionshandler.cpp
emojimodel.cpp
models/emojimodel.cpp
emojitones.cpp
customemojimodel.cpp
models/customemojimodel.cpp
clipboard.cpp
matriximageprovider.cpp
messageeventmodel.cpp
messagefiltermodel.cpp
roomlistmodel.cpp
sortfilterspacelistmodel.cpp
models/messageeventmodel.cpp
models/messagefiltermodel.cpp
models/roomlistmodel.cpp
models/sortfilterspacelistmodel.cpp
spacehierarchycache.cpp
roommanager.cpp
neochatroom.cpp
neochatuser.cpp
userlistmodel.cpp
userfiltermodel.cpp
publicroomlistmodel.cpp
userdirectorylistmodel.cpp
keywordnotificationrulemodel.cpp
models/userlistmodel.cpp
models/userfiltermodel.cpp
models/publicroomlistmodel.cpp
models/userdirectorylistmodel.cpp
models/keywordnotificationrulemodel.cpp
utils.cpp
notificationsmanager.cpp
sortfilterroomlistmodel.cpp
models/sortfilterroomlistmodel.cpp
chatdocumenthandler.cpp
devicesmodel.cpp
models/devicesmodel.cpp
filetypesingleton.cpp
login.cpp
stickerevent.cpp
webshortcutmodel.cpp
models/webshortcutmodel.cpp
blurhash.cpp
blurhashimageprovider.cpp
joinrulesevent.cpp
collapsestateproxymodel.cpp
models/collapsestateproxymodel.cpp
urlhelper.cpp
windowcontroller.cpp
linkpreviewer.cpp
completionmodel.cpp
completionproxymodel.cpp
actionsmodel.cpp
serverlistmodel.cpp
statemodel.cpp
models/completionmodel.cpp
models/completionproxymodel.cpp
models/actionsmodel.cpp
models/serverlistmodel.cpp
models/statemodel.cpp
filetransferpseudojob.cpp
searchmodel.cpp
models/searchmodel.cpp
)
add_executable(neochat-app
@@ -79,7 +79,7 @@ if(NOT ANDROID)
else()
target_sources(neochat PRIVATE trayicon.cpp)
endif()
target_link_libraries(neochat PUBLIC KF5::ConfigWidgets KF5::WindowSystem)
target_link_libraries(neochat PUBLIC KF${QT_MAJOR_VERSION}::ConfigWidgets KF${QT_MAJOR_VERSION}::WindowSystem)
target_compile_definitions(neochat PUBLIC -DHAVE_COLORSCHEME)
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
endif()
@@ -87,13 +87,14 @@ endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
target_sources(neochat-app PRIVATE res_desktop.qrc)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11)
target_sources(neochat PRIVATE runner.cpp)
else()
target_sources(neochat-app PRIVATE res_android.qrc)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::SonnetCore KF5::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
if(NEOCHAT_FLATPAK)
@@ -103,6 +104,9 @@ endif()
if(ANDROID)
target_sources(neochat PRIVATE notifyrc.qrc)
target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL)
if(SQLite3_FOUND)
target_link_libraries(neochat-app PRIVATE SQLite::SQLite3)
endif()
target_sources(neochat-app PRIVATE notifyrc.qrc)
target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL)
kirigami_package_breeze_icons(ICONS
@@ -159,7 +163,7 @@ if(ANDROID)
"zoom-out"
"image-rotate-left-symbolic"
"image-rotate-right-symbolic"
"channel-insecure-symbolic"
"channel-secure-symbolic"
"download"
"smiley"
"tools-check-spelling"
@@ -173,7 +177,7 @@ if(ANDROID)
"visibility"
)
else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF5::KIOWidgets)
target_link_libraries(neochat PUBLIC Qt::Widgets KF${QT_MAJOR_VERSION}::KIOWidgets)
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
endif()
@@ -181,12 +185,12 @@ if(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF5::DBusAddons)
target_link_libraries(neochat PUBLIC KF5::DBusAddons)
if(TARGET KF${QT_MAJOR_VERSION}::DBusAddons)
target_link_libraries(neochat PUBLIC KF${QT_MAJOR_VERSION}::DBusAddons)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()
if (TARGET KF5::KIOWidgets)
if (TARGET KF${QT_MAJOR_VERSION}::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()

View File

@@ -13,10 +13,11 @@
#include <KLocalizedString>
#include <QStringBuilder>
#include "actionsmodel.h"
#include "controller.h"
#include "customemojimodel.h"
#include "models/actionsmodel.h"
#include "models/customemojimodel.h"
#include "neochatconfig.h"
#include "neochatroom.h"
#include "neochatuser.h"
#include "roommanager.h"
@@ -58,9 +59,9 @@ void ActionsHandler::setRoom(NeoChatRoom *room)
Q_EMIT roomChanged();
}
void ActionsHandler::handleMessage()
void ActionsHandler::handleNewMessage()
{
checkEffects();
checkEffects(m_room->chatBoxText());
if (!m_room->chatBoxAttachmentPath().isEmpty()) {
QUrl url(m_room->chatBoxAttachmentPath());
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
@@ -69,13 +70,39 @@ void ActionsHandler::handleMessage()
m_room->setChatBoxText({});
return;
}
QString handledText = m_room->chatBoxText();
std::sort(m_room->mentions()->begin(), m_room->mentions()->end(), [](const auto &a, const auto &b) -> bool {
QString handledText = m_room->chatBoxText();
handledText = handleMentions(handledText);
handleMessage(m_room->chatBoxText(), handledText);
}
void ActionsHandler::handleEdit()
{
checkEffects(m_room->editText());
QString handledText = m_room->editText();
handledText = handleMentions(handledText, true);
handleMessage(m_room->editText(), handledText, true);
}
QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
{
if (!m_room) {
return QString();
}
QVector<Mention> *mentions;
if (isEdit) {
mentions = m_room->editMentions();
} else {
mentions = m_room->mentions();
}
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
return a.cursor.anchor() > b.cursor.anchor();
});
for (const auto &mention : *m_room->mentions()) {
for (const auto &mention : *mentions) {
if (mention.text.isEmpty() || mention.id.isEmpty()) {
continue;
}
@@ -83,11 +110,16 @@ void ActionsHandler::handleMessage()
mention.cursor.position() - mention.cursor.anchor(),
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text, mention.id));
}
m_room->mentions()->clear();
mentions->clear();
return handledText;
}
void ActionsHandler::handleMessage(const QString &text, QString handledText, const bool &isEdit)
{
if (NeoChatConfig::allowQuickEdit()) {
QRegularExpression sed("^s/([^/]*)/([^/]*)(/g)?$");
auto match = sed.match(m_room->chatBoxText());
auto match = sed.match(text);
if (match.hasMatch()) {
const QString regex = match.captured(1);
const QString replacement = match.captured(2).toHtmlEscaped();
@@ -146,13 +178,13 @@ void ActionsHandler::handleMessage()
if (handledText.length() == 0) {
return;
}
m_room->postMessage(m_room->chatBoxText(), handledText, messageType, m_room->chatBoxReplyId(), m_room->chatBoxEditId());
m_room->postMessage(text, handledText, messageType, m_room->chatBoxReplyId(), isEdit ? m_room->chatBoxEditId() : "");
}
void ActionsHandler::checkEffects()
void ActionsHandler::checkEffects(const QString &text)
{
std::optional<QString> effect = std::nullopt;
const auto &text = m_room->chatBoxText();
if (text.contains("\u2744")) {
effect = QLatin1String("snowflake");
} else if (text.contains("\u1F386")) {

View File

@@ -33,14 +33,22 @@ Q_SIGNALS:
public Q_SLOTS:
/// \brief Post a message.
///
/// This also interprets commands if any.
void handleMessage();
/**
* @brief Pre-process text and send message.
*/
void handleNewMessage();
/**
* @brief Pre-process text and send edit.
*/
void handleEdit();
private:
NeoChatRoom *m_room = nullptr;
void checkEffects();
void checkEffects(const QString &text);
QString handleMentions(QString handledText, const bool &isEdit = false);
void handleMessage(const QString &text, QString handledText, const bool &isEdit = false);
};
QString markdownToHTML(const QString &markdown);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "blurhashimageprovider.h"

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once

View File

@@ -14,9 +14,9 @@
#include <Sonnet/BackgroundChecker>
#include <Sonnet/Settings>
#include "actionsmodel.h"
#include "models/actionsmodel.h"
#include "models/roomlistmodel.h"
#include "neochatroom.h"
#include "roomlistmodel.h"
class SyntaxHighlighter : public QSyntaxHighlighter
{
@@ -105,14 +105,19 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
{
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
m_completionModel->setRoom(m_room);
static NeoChatRoom *previousRoom = nullptr;
static QPointer<NeoChatRoom> previousRoom = nullptr;
if (previousRoom) {
disconnect(previousRoom, &NeoChatRoom::chatBoxTextChanged, this, nullptr);
disconnect(previousRoom, &NeoChatRoom::editTextChanged, this, nullptr);
}
previousRoom = m_room;
connect(m_room, &NeoChatRoom::chatBoxTextChanged, this, [this]() {
int start = completionStartIndex();
m_completionModel->setText(m_room->chatBoxText().mid(start, cursorPosition() - start), m_room->chatBoxText().mid(start));
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
connect(m_room, &NeoChatRoom::editTextChanged, this, [this]() {
int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
});
connect(this, &ChatDocumentHandler::documentChanged, this, [this]() {
@@ -123,7 +128,7 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
return;
}
int start = completionStartIndex();
m_completionModel->setText(m_room->chatBoxText().mid(start, cursorPosition() - start), m_room->chatBoxText().mid(start));
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
}
@@ -138,7 +143,7 @@ int ChatDocumentHandler::completionStartIndex() const
#else
const auto cursor = cursorPosition();
#endif
const auto &text = m_room->chatBoxText();
const auto &text = getText();
auto start = std::min(cursor, text.size()) - 1;
while (start > -1) {
if (text.at(start) == QLatin1Char(' ')) {
@@ -150,6 +155,20 @@ int ChatDocumentHandler::completionStartIndex() const
return start;
}
bool ChatDocumentHandler::isEdit() const
{
return m_isEdit;
}
void ChatDocumentHandler::setIsEdit(bool edit)
{
if (edit == m_isEdit) {
return;
}
m_isEdit = edit;
Q_EMIT isEditChanged();
}
QQuickTextDocument *ChatDocumentHandler::document() const
{
return m_document;
@@ -204,7 +223,7 @@ void ChatDocumentHandler::complete(int index)
if (m_completionModel->autoCompletionType() == CompletionModel::User) {
auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Text).toString();
auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString();
auto text = m_room->chatBoxText();
auto text = getText();
auto at = text.lastIndexOf(QLatin1Char('@'), cursorPosition() - 1);
QTextCursor cursor(document()->textDocument());
cursor.setPosition(at);
@@ -213,11 +232,11 @@ void ChatDocumentHandler::complete(int index)
cursor.setPosition(at);
cursor.setPosition(cursor.position() + name.size(), QTextCursor::KeepAnchor);
cursor.setKeepPositionOnInsert(true);
m_room->mentions()->push_back({cursor, name, 0, 0, id});
pushMention({cursor, name, 0, 0, id});
m_highlighter->rehighlight();
} else if (m_completionModel->autoCompletionType() == CompletionModel::Command) {
auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString();
auto text = m_room->chatBoxText();
auto text = getText();
auto at = text.lastIndexOf(QLatin1Char('/'));
QTextCursor cursor(document()->textDocument());
cursor.setPosition(at);
@@ -225,7 +244,7 @@ void ChatDocumentHandler::complete(int index)
cursor.insertText(QStringLiteral("/%1 ").arg(command));
} else if (m_completionModel->autoCompletionType() == CompletionModel::Room) {
auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString();
auto text = m_room->chatBoxText();
auto text = getText();
auto at = text.lastIndexOf(QLatin1Char('#'), cursorPosition() - 1);
QTextCursor cursor(document()->textDocument());
cursor.setPosition(at);
@@ -234,11 +253,11 @@ void ChatDocumentHandler::complete(int index)
cursor.setPosition(at);
cursor.setPosition(cursor.position() + alias.size(), QTextCursor::KeepAnchor);
cursor.setKeepPositionOnInsert(true);
m_room->mentions()->push_back({cursor, alias, 0, 0, alias});
pushMention({cursor, alias, 0, 0, alias});
m_highlighter->rehighlight();
} else if (m_completionModel->autoCompletionType() == CompletionModel::Emoji) {
auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString();
auto text = m_room->chatBoxText();
auto text = getText();
auto at = text.lastIndexOf(QLatin1Char(':'));
QTextCursor cursor(document()->textDocument());
cursor.setPosition(at);
@@ -281,3 +300,27 @@ void ChatDocumentHandler::setSelectionEnd(int position)
m_selectionEnd = position;
Q_EMIT selectionEndChanged();
}
QString ChatDocumentHandler::getText() const
{
if (!m_room) {
return QString();
}
if (m_isEdit) {
return m_room->editText();
} else {
return m_room->chatBoxText();
}
}
void ChatDocumentHandler::pushMention(const Mention mention) const
{
if (!m_room) {
return;
}
if (m_isEdit) {
m_room->editMentions()->push_back(mention);
} else {
m_room->mentions()->push_back(mention);
}
}

View File

@@ -7,8 +7,9 @@
#include <QQuickTextDocument>
#include <QTextCursor>
#include "completionmodel.h"
#include "userlistmodel.h"
#include "models/completionmodel.h"
#include "models/userlistmodel.h"
#include "neochatroom.h"
class QTextDocument;
class NeoChatRoom;
@@ -17,6 +18,14 @@ class SyntaxHighlighter;
class ChatDocumentHandler : public QObject
{
Q_OBJECT
/**
* @brief Is the instance being used to handle an edit message.
*
* This is needed to ensure that the text and mentions are saved and retrieved
* from the correct parameters in the assigned room.
*/
Q_PROPERTY(bool isEdit READ isEdit WRITE setIsEdit NOTIFY isEditChanged)
Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged)
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged)
@@ -24,11 +33,14 @@ class ChatDocumentHandler : public QObject
Q_PROPERTY(CompletionModel *completionModel READ completionModel NOTIFY completionModelChanged)
Q_PROPERTY(NeoChatRoom *room READ room NOTIFY roomChanged)
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
public:
explicit ChatDocumentHandler(QObject *parent = nullptr);
[[nodiscard]] bool isEdit() const;
void setIsEdit(bool edit);
[[nodiscard]] QQuickTextDocument *document() const;
void setDocument(QQuickTextDocument *document);
@@ -49,6 +61,7 @@ public:
void updateCompletions();
CompletionModel *completionModel() const;
Q_SIGNALS:
void isEditChanged();
void documentChanged();
void cursorPositionChanged();
void roomChanged();
@@ -59,6 +72,8 @@ Q_SIGNALS:
private:
int completionStartIndex() const;
bool m_isEdit;
QQuickTextDocument *m_document;
NeoChatRoom *m_room = nullptr;
@@ -68,6 +83,9 @@ private:
int m_selectionStart;
int m_selectionEnd;
QString getText() const;
void pushMention(const Mention mention) const;
SyntaxHighlighter *m_highlighter = nullptr;
CompletionModel::AutoCompletionType m_completionType = CompletionModel::None;

View File

@@ -33,13 +33,14 @@ QImage Clipboard::image() const
QString Clipboard::saveImage(QString localPath) const
{
if (!QDir().exists(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)))) {
QDir().mkdir(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
QString imageDir(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
if (!QDir().exists(imageDir)) {
QDir().mkdir(imageDir);
}
if (localPath.isEmpty()) {
localPath = QStringLiteral("file://%1/screenshots/%2.png")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd-hh-mm-ss")));
localPath = QStringLiteral("file://%1/%2.png").arg(imageDir, QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd-hh-mm-ss")));
}
QUrl url(localPath);
if (!url.isLocalFile()) {
@@ -51,21 +52,30 @@ QString Clipboard::saveImage(QString localPath) const
return {};
}
QDir dir;
if (!dir.exists(localPath)) {
dir.mkpath(localPath);
if (image.save(url.toLocalFile())) {
return localPath;
} else {
return {};
}
image.save(url.toLocalFile());
return localPath;
}
void Clipboard::saveText(QString message)
{
QRegularExpression re("<[^>]*>");
static QRegularExpression re(QStringLiteral("<[^>]*>"));
auto *mineData = new QMimeData; // ownership is transferred to clipboard
mineData->setHtml(message);
mineData->setText(message.replace(re, ""));
mineData->setText(message.replace(re, QString()));
m_clipboard->setMimeData(mineData);
}
void Clipboard::setImage(const QUrl &url)
{
if (url.isLocalFile()) {
QImage img(url.path());
auto *mimeData = new QMimeData;
mimeData->setImageData(img);
if (!img.isNull()) {
m_clipboard->setMimeData(mimeData);
}
}
}

View File

@@ -26,6 +26,7 @@ public:
Q_INVOKABLE QString saveImage(QString localPath = {}) const;
Q_INVOKABLE void saveText(QString message);
Q_INVOKABLE void setImage(const QUrl &image);
private:
QClipboard *m_clipboard;

View File

@@ -1,85 +0,0 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "collapsestateproxymodel.h"
#include <KLocalizedString>
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent);
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool() // If it's a new day, show it
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventResolvedTypeRole)
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0),
MessageEventModel::EventResolvedTypeRole) // Also show it if it's of a different type than the one before TODO improve in
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::AuthorIdRole)
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::AuthorIdRole); // Also show it if it's a different author
}
QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
{
if (role == AggregateDisplayRole) {
return aggregateEventToString(mapToSource(index).row());
}
return sourceModel()->data(mapToSource(index), role);
}
QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
{
auto roles = sourceModel()->roleNames();
roles[AggregateDisplayRole] = "aggregateDisplay";
return roles;
}
QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
{
QStringList parts;
for (int i = sourceRow; i >= 0; i--) {
parts += sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString();
if (sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventTypeRole)
!= MessageEventModel::DelegateType::State // If it's not a state event
|| (i > 0
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventResolvedTypeRole)
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventResolvedTypeRole)) // or of a different type
|| (i > 0
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorIdRole)
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::AuthorIdRole)) // or by a different author
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
) {
break;
}
}
if (!parts.isEmpty()) {
QStringList chunks;
while (!parts.isEmpty()) {
chunks += QString();
int count = 1;
auto part = parts.takeFirst();
chunks.last() += part;
while (!parts.isEmpty() && parts.first() == part) {
parts.removeFirst();
count++;
}
if (count > 1) {
chunks.last() += i18ncp("[user did something] n times", " %1 time", " %1 times", count);
}
}
QString text = chunks.takeFirst();
if (chunks.size() > 0) {
while (chunks.size() > 1) {
text += i18nc("[action 1], [action 2 and action 3]", ", ");
text += chunks.takeFirst();
}
text += i18nc("[action 1, action 2] and [action 3]", " and ");
text += chunks.takeFirst();
}
return text;
} else {
return {};
}
}

View File

@@ -1,21 +0,0 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include "messageeventmodel.h"
#include <QSortFilterProxyModel>
class CollapseStateProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
enum Roles {
AggregateDisplayRole = MessageEventModel::LastRole + 1,
};
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
[[nodiscard]] QString aggregateEventToString(int row) const;
};

View File

@@ -1,10 +1,14 @@
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-3.0-only
#include "controller.h"
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <qt5keychain/keychain.h>
#else
#include <qt6keychain/keychain.h>
#endif
#include <KConfig>
#include <KConfigGroup>
@@ -37,6 +41,7 @@
#include <csapi/content-repo.h>
#include <csapi/logout.h>
#include <csapi/profile.h>
#include <jobs/downloadfilejob.h>
#include <qt_connection_util.h>
#ifdef QUOTIENT_07
@@ -128,16 +133,7 @@ Controller::Controller(QObject *parent)
if (AccountRegistry::instance().size() > oldAccountCount) {
auto connection = AccountRegistry::instance().accounts()[AccountRegistry::instance().size() - 1];
connect(connection, &Connection::syncDone, this, [=]() {
bool changes = false;
for (const auto &room : connection->allRooms()) {
if (m_notificationCounts[room] != room->unreadStats().notableCount) {
m_notificationCounts[room] = room->unreadStats().notableCount;
changes = true;
}
}
if (changes) {
handleNotifications();
}
handleNotifications(connection);
});
}
oldAccountCount = AccountRegistry::instance().size();
@@ -146,19 +142,16 @@ Controller::Controller(QObject *parent)
}
#ifdef QUOTIENT_07
void Controller::handleNotifications()
void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
{
static bool initial = true;
static QStringList initial;
static QStringList oldNotifications;
if (!m_connection) {
return;
}
auto job = m_connection->callApi<GetNotificationsJob>();
auto job = connection->callApi<GetNotificationsJob>();
connect(job, &BaseJob::success, this, [this, job]() {
connect(job, &BaseJob::success, this, [job, connection]() {
const auto notifications = job->jsonData()["notifications"].toArray();
if (initial) {
initial = false;
if (!initial.contains(connection->user()->id())) {
initial.append(connection->user()->id());
for (const auto &n : notifications) {
oldNotifications += n.toObject()["event"].toObject()["event_id"].toString();
}
@@ -174,10 +167,12 @@ void Controller::handleNotifications()
continue;
}
oldNotifications += notification["event"].toObject()["event_id"].toString();
auto room = m_connection->room(notification["room_id"].toString());
auto room = connection->room(notification["room_id"].toString());
// If room exists, room is NOT active OR the application is NOT active, show notification
if (room && !(room->id() == RoomManager::instance().currentRoom()->id() && QGuiApplication::applicationState() == Qt::ApplicationActive)) {
if (room
&& !(RoomManager::instance().currentRoom() && room->id() == RoomManager::instance().currentRoom()->id()
&& QGuiApplication::applicationState() == Qt::ApplicationActive)) {
// The room might have been deleted (for example rejected invitation).
auto sender = room->user(notification["event"].toObject()["sender"].toString());
@@ -196,7 +191,7 @@ void Controller::handleNotifications()
if (notification["event"]["type"] == "m.room.encrypted") {
#ifdef Quotient_E2EE_ENABLED
auto decrypted = m_connection->decryptNotification(notification);
auto decrypted = connection->decryptNotification(notification);
body = decrypted["content"].toObject()["body"].toString();
#endif
if (body.isEmpty()) {
@@ -381,6 +376,11 @@ void Controller::invokeLogin()
if (error == "Unrecognised access token") {
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
logout(connection, false);
} else if (error == "Connection closed") {
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
// Failed due to network connection issue. This might happen when the homeserver is
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
// connect to the homeserver. In this case, we don't want to do logout().
} else {
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
logout(connection, true);
@@ -611,6 +611,11 @@ void Controller::setActiveConnection(Connection *connection)
m_isOnline = true;
Q_EMIT isOnlineChanged(true);
});
connect(connection, &Connection::requestFailed, this, [=](BaseJob *job) {
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"].toString() == "M_TOO_LARGE"_ls) {
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
}
});
} else {
NeoChatConfig::self()->setActiveConnection(QString());
}
@@ -639,9 +644,17 @@ NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Om
void Controller::createRoom(const QString &name, const QString &topic)
{
auto createRoomJob = m_connection->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
Quotient::CreateRoomJob::connect(createRoomJob, &CreateRoomJob::failure, [this, createRoomJob] {
connect(createRoomJob, &CreateRoomJob::failure, this, [this, createRoomJob] {
Q_EMIT errorOccured(i18n("Room creation failed: \"%1\"", createRoomJob->errorString()));
});
connectSingleShot(
this,
&Controller::roomAdded,
this,
[this](NeoChatRoom *room) {
RoomManager::instance().enterRoom(room);
},
Qt::QueuedConnection);
}
bool Controller::isOnline() const

View File

@@ -119,7 +119,7 @@ private:
bool hasWindowSystem() const;
#ifdef QUOTIENT_07
void handleNotifications();
void handleNotifications(QPointer<Quotient::Connection> connection);
#endif
private Q_SLOTS:
@@ -151,6 +151,7 @@ Q_SIGNALS:
void keyVerificationAccept(const QString &commitment);
void keyVerificationKey(const QString &sas);
void activeConnectionIndexChanged();
void roomAdded(NeoChatRoom *room);
public Q_SLOTS:
void logout(Quotient::Connection *conn, bool serverSideLogout);

View File

@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "emojitones.h"
#include "emojimodel.h"
#include "models/emojimodel.h"
QMultiHash<QString, QVariant> EmojiTones::_tones = {
#include "emojitones_data.h"

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "filetransferpseudojob.h"

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "login.h"

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

View File

@@ -42,39 +42,39 @@
#include "blurhashimageprovider.h"
#include "chatdocumenthandler.h"
#include "clipboard.h"
#include "collapsestateproxymodel.h"
#include "controller.h"
#include "customemojimodel.h"
#include "devicesmodel.h"
#include "emojimodel.h"
#include "filetypesingleton.h"
#include "joinrulesevent.h"
#include "linkpreviewer.h"
#include "keywordnotificationrulemodel.h"
#include "login.h"
#include "matriximageprovider.h"
#include "messageeventmodel.h"
#include "messagefiltermodel.h"
#include "models/collapsestateproxymodel.h"
#include "models/customemojimodel.h"
#include "models/devicesmodel.h"
#include "models/emojimodel.h"
#include "models/keywordnotificationrulemodel.h"
#include "models/messageeventmodel.h"
#include "models/messagefiltermodel.h"
#include "models/publicroomlistmodel.h"
#include "models/roomlistmodel.h"
#include "models/searchmodel.h"
#include "models/serverlistmodel.h"
#include "models/sortfilterroomlistmodel.h"
#include "models/sortfilterspacelistmodel.h"
#include "models/userdirectorylistmodel.h"
#include "models/userfiltermodel.h"
#include "models/userlistmodel.h"
#include "models/webshortcutmodel.h"
#include "neochatconfig.h"
#include "neochatroom.h"
#include "neochatuser.h"
#include "notificationsmanager.h"
#include "searchmodel.h"
#ifdef QUOTIENT_07
#include "pollhandler.h"
#endif
#include "publicroomlistmodel.h"
#include "roomlistmodel.h"
#include "roommanager.h"
#include "serverlistmodel.h"
#include "sortfilterroomlistmodel.h"
#include "sortfilterspacelistmodel.h"
#include "spacehierarchycache.h"
#include "urlhelper.h"
#include "userdirectorylistmodel.h"
#include "userfiltermodel.h"
#include "userlistmodel.h"
#include "webshortcutmodel.h"
#include "windowcontroller.h"
#ifdef QUOTIENT_07
#include <keyverificationsession.h>
@@ -82,15 +82,19 @@
#ifdef HAVE_COLORSCHEME
#include "colorschemer.h"
#endif
#include "completionmodel.h"
#include "models/completionmodel.h"
#include "models/statemodel.h"
#include "neochatuser.h"
#include "statemodel.h"
#ifdef HAVE_RUNNER
#include "runner.h"
#include <QDBusConnection>
#endif
#ifdef Q_OS_WINDOWS
#include <Windows.h>
#endif
using namespace Quotient;
class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
@@ -153,7 +157,7 @@ int main(int argc, char *argv[])
KAboutLicense::GPL_V3,
i18n("© 2018-2020 Black Hat, 2020-2022 KDE Community"));
about.addAuthor(i18n("Carl Schwan"), i18n("Maintainer"), QStringLiteral("carl@carlschwan.eu"), QStringLiteral("https://carlschwan.eu"));
about.addAuthor(i18n("Tobias Fella"), i18n("Maintainer"), QStringLiteral("fella@posteo.de"), QStringLiteral("https://tobiasfella.de"));
about.addAuthor(i18n("Tobias Fella"), i18n("Maintainer"), QStringLiteral("tobias.fella@kde.org"), QStringLiteral("https://tobiasfella.de"));
about.addAuthor(i18n("James Graham"), i18n("Maintainer"), QStringLiteral("james.h.graham@protonmail.com"));
about.addCredit(i18n("Black Hat"), i18n("Original author of Spectral"), QStringLiteral("bhat@encom.eu.org"));
about.addCredit(i18n("Alexey Rusakov"), i18n("Maintainer of Quotient"), QStringLiteral("Kitsune-Ral@users.sf.net"));

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "actionsmodel.h"
@@ -115,7 +115,7 @@ QVector<ActionsModel::Action> actions{
QStringLiteral("spoiler"),
[](const QString &text, NeoChatRoom *room) {
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
room->postMessage(QStringLiteral("/spoiler %1").arg(text),
QStringLiteral("<span data-mx-spoiler>%1</span>").arg(text),
RoomMessageEvent::MsgType::Text,
room->chatBoxReplyId(),
@@ -158,11 +158,12 @@ QVector<ActionsModel::Action> actions{
return QString();
}
#ifdef QUOTIENT_07
if (room->currentState().get<RoomMemberEvent>(text)->membership() == Membership::Invite) {
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
return QString();
}
if (room->currentState().get<RoomMemberEvent>(text)->membership() == Membership::Ban) {
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Ban) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
return QString();
}
@@ -208,6 +209,40 @@ QVector<ActionsModel::Action> actions{
kli18n("<room alias or id>"),
kli18n("Joins the given room"),
},
#ifdef QUOTIENT_07
Action{
QStringLiteral("knock"),
[](const QString &text, NeoChatRoom *room) {
auto parts = text.split(QLatin1String(" "));
QString roomName = parts[0];
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
auto regexMatch = roomRegex.match(roomName);
if (!regexMatch.hasMatch()) {
Q_EMIT room->showMessage(NeoChatRoom::Error,
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
if (targetRoom) {
RoomManager::instance().enterRoom(dynamic_cast<NeoChatRoom *>(targetRoom));
return QString();
}
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
auto connection = Controller::instance().activeConnection();
const auto knownServer = roomName.mid(roomName.indexOf(":") + 1);
if (parts.length() >= 2) {
RoomManager::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});
} else {
RoomManager::instance().knockRoom(connection, roomName, QString(), QStringList{knownServer});
}
return QString();
},
false,
std::nullopt,
kli18n("<room alias or id> [<reason>]"),
kli18n("Requests to join the given room"),
},
#endif
Action{
QStringLiteral("j"),
[](const QString &text, NeoChatRoom *room) {

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once

View File

@@ -0,0 +1,134 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "collapsestateproxymodel.h"
#include "messageeventmodel.h"
#include <KLocalizedString>
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent);
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
}
QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
{
if (role == AggregateDisplayRole) {
return aggregateEventToString(mapToSource(index).row());
} else if (role == StateEventsRole) {
return stateEventsList(mapToSource(index).row());
} else if (role == AuthorListRole) {
return authorList(mapToSource(index).row());
}
return sourceModel()->data(mapToSource(index), role);
}
QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
{
auto roles = sourceModel()->roleNames();
roles[AggregateDisplayRole] = "aggregateDisplay";
roles[StateEventsRole] = "stateEvents";
roles[AuthorListRole] = "authorList";
return roles;
}
QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
{
QStringList parts;
QVariantList uniqueAuthors;
for (int i = sourceRow; i >= 0; i--) {
parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
if (!uniqueAuthors.contains(nextAuthor)) {
uniqueAuthors.append(nextAuthor);
}
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
!= MessageEventModel::DelegateType::State // If it's not a state event
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
) {
break;
}
}
parts.sort(); // Sort them so that all identical events can be collected.
if (!parts.isEmpty()) {
QStringList chunks;
while (!parts.isEmpty()) {
chunks += QString();
int count = 1;
auto part = parts.takeFirst();
chunks.last() += part;
while (!parts.isEmpty() && parts.first() == part) {
parts.removeFirst();
count++;
}
if (count > 1 && uniqueAuthors.length() == 1) {
chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count);
}
}
chunks.removeDuplicates();
QString text = "<style>a {text-decoration: none;}</style>"; // There can be links in the event text so make sure all are styled.
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
.arg(uniqueAuthors[0].toMap()["id"].toString(),
uniqueAuthors[0].toMap()["color"].toString(),
uniqueAuthors[0].toMap()["displayName"].toString());
text += userText;
text += chunks.takeFirst();
if (chunks.size() > 0) {
while (chunks.size() > 1) {
text += i18nc("[action 1], [action 2 and/or action 3]", ", ");
text += chunks.takeFirst();
}
text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
text += chunks.takeFirst();
}
return text;
} else {
return {};
}
}
QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
{
QVariantList stateEvents;
for (int i = sourceRow; i >= 0; i--) {
auto nextState = QVariantMap{
{"author", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
{"authorDisplayName", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
};
stateEvents.append(nextState);
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
!= MessageEventModel::DelegateType::State // If it's not a state event
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
) {
break;
}
}
return stateEvents;
}
QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
{
QVariantList uniqueAuthors;
for (int i = sourceRow; i >= 0; i--) {
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
if (!uniqueAuthors.contains(nextAvatar)) {
uniqueAuthors.append(nextAvatar);
}
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
!= MessageEventModel::DelegateType::State // If it's not a state event
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
) {
break;
}
}
return uniqueAuthors;
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include "messageeventmodel.h"
#include <QSortFilterProxyModel>
class CollapseStateProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
enum Roles {
AggregateDisplayRole = MessageEventModel::LastRole + 1,
StateEventsRole,
AuthorListRole,
};
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
/**
* @brief QString aggregating the text of consecutive state events starting at row.
*
* If state events happen on different days they will be split into two aggregate
* events.
*/
[[nodiscard]] QString aggregateEventToString(int row) const;
/**
* @brief Return a list of consecutive state events starting at row.
*
* If state events happen on different days they will be split into two aggregate
* events.
*/
[[nodiscard]] QVariantList stateEventsList(int row) const;
/**
* @brief List of unique authors for the aggregate state events starting at row.
*/
[[nodiscard]] QVariantList authorList(int row) const;
};

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "completionmodel.h"
@@ -16,7 +16,7 @@ CompletionModel::CompletionModel(QObject *parent)
: QAbstractListModel(parent)
, m_filterModel(new CompletionProxyModel())
, m_userListModel(new UserListModel(this))
, m_emojiModel(new KConcatenateRowsProxyModel(this))
, m_emojiModel(new QConcatenateTablesProxyModel(this))
{
connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion);
connect(this, &CompletionModel::roomChanged, this, [this]() {
@@ -145,7 +145,7 @@ void CompletionModel::updateCompletion()
m_filterModel->setFullText(m_fullText);
m_filterModel->setFilterText(m_text);
m_filterModel->invalidate();
} else if (text().startsWith(QLatin1Char(':'))
} else if (text().startsWith(QLatin1Char(':')) && !text()[1].isUpper()
&& (m_fullText.indexOf(QLatin1Char(':'), 1) == -1
|| (m_fullText.indexOf(QLatin1Char(' ')) != -1 && m_fullText.indexOf(QLatin1Char(':'), 1) > m_fullText.indexOf(QLatin1Char(' '), 1)))) {
m_filterModel->setSourceModel(m_emojiModel);

View File

@@ -1,12 +1,11 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QConcatenateTablesProxyModel>
#include <QSortFilterProxyModel>
#include <KConcatenateRowsProxyModel>
#include "roomlistmodel.h"
class CompletionProxyModel;
@@ -75,6 +74,6 @@ private:
UserListModel *m_userListModel;
RoomListModel *m_roomListModel;
KConcatenateRowsProxyModel *m_emojiModel;
QConcatenateTablesProxyModel *m_emojiModel;
};
Q_DECLARE_METATYPE(CompletionModel::AutoCompletionType);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "completionproxymodel.h"

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once

View File

@@ -4,8 +4,8 @@
#pragma once
#include <QAbstractListModel>
#include <memory>
#include <QRegularExpression>
#include <memory>
struct CustomEmoji {
QString name; // with :semicolons:

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "devicesmodel.h"
@@ -70,7 +70,12 @@ void DevicesModel::logout(int index, const QString &password)
auto job = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
connect(job, &BaseJob::result, this, [this, job, password, index] {
if (job->error() != 0) {
auto onSuccess = [this, index]() {
beginRemoveRows(QModelIndex(), index, index);
m_devices.remove(index);
endRemoveRows();
};
if (job->error() != BaseJob::Success) {
QJsonObject replyData = job->jsonData();
QJsonObject authData;
authData["session"] = replyData["session"];
@@ -79,11 +84,9 @@ void DevicesModel::logout(int index, const QString &password)
QJsonObject identifier = {{"type", "m.id.user"}, {"user", Controller::instance().activeConnection()->user()->id()}};
authData["identifier"] = identifier;
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
connect(innerJob, &BaseJob::success, this, [this, index]() {
beginRemoveRows(QModelIndex(), index, index);
m_devices.remove(index);
endRemoveRows();
});
connect(innerJob, &BaseJob::success, this, onSuccess);
} else {
onSuccess();
}
});
}

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

View File

@@ -67,6 +67,8 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
roles[IsNameChangeRole] = "isNameChange";
roles[IsAvatarChangeRole] = "isAvatarChange";
roles[IsRedactedRole] = "isRedacted";
roles[GenericDisplayRole] = "genericDisplay";
roles[IsPendingRole] = "isPending";
return roles;
}
@@ -175,6 +177,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
});
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent *, int i) {
Q_EMIT dataChanged(index(i, 0), index(i, 0), {IsPendingRole});
if (i == 0) {
return; // No need to move anything, just refresh
}
@@ -439,7 +442,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
case EventTypeRole:
return DelegateType::ReadMarker;
case TimeRole: {
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime();
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
const KFormat format;
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
}
@@ -462,6 +465,14 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return m_currentRoom->eventToString(evt, Qt::RichText);
}
if (role == GenericDisplayRole) {
if (evt.isRedacted()) {
return i18n("<i>[This message was deleted]</i>");
}
return m_currentRoom->eventToGenericString(evt);
}
if (role == FormattedBodyRole) {
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
if (e->hasTextContent() && e->mimeType().name() != "text/plain") {
@@ -560,7 +571,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == HighlightRole) {
return m_currentRoom->isEventHighlighted(&evt);
return !m_currentRoom->isDirectChat() && m_currentRoom->isEventHighlighted(&evt);
}
if (role == FileMimetypeIcon) {
@@ -899,6 +910,10 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return evt.isRedacted();
}
if (role == IsPendingRole) {
return row < m_currentRoom->pendingEvents().size();
}
return {};
}
@@ -937,24 +952,12 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
if (content.contains("m.new_content")) {
// The message has been edited so we have to return the id of the original message instead of the replacement
eventId = content["m.relates_to"].toObject()["event_id"].toString();
e = eventCast<const RoomMessageEvent>(m_currentRoom->findInTimeline(eventId)->event());
if (!e) {
return {};
}
content = e->contentJson();
} else {
// For any message that isn't an edit return the id of the current message
eventId = (*it)->id();
}
targetMessage.insert("event_id", eventId);
targetMessage.insert("formattedBody", content["formatted_body"].toString());
// keep reply relationship
if (content.contains("m.relates_to")) {
targetMessage.insert("m.relates_to", content["m.relates_to"].toObject());
}
// Need to get the message from the original eventId or body will have * on the front
QModelIndex idx = index(eventIDToIndex(eventId), 0);
targetMessage.insert("message", idx.data(Qt::UserRole + 2));
@@ -978,6 +981,9 @@ QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline)
for (auto it = timelineBottom; it != limit; ++it) {
auto evt = it->event();
auto e = eventCast<const RoomMessageEvent>(evt);
if (!e) {
continue;
}
auto content = (*it)->contentJson();

View File

@@ -45,6 +45,7 @@ public:
AnnotationRole,
UserMarkerRole,
FormattedBodyRole,
GenericDisplayRole,
MimeTypeRole,
FileMimetypeIcon,
@@ -72,6 +73,7 @@ public:
IsNameChangeRole,
IsAvatarChangeRole,
IsRedactedRole,
IsPendingRole,
LastRole, // Keep this last
};
Q_ENUM(EventRoles)

View File

@@ -3,6 +3,7 @@
#include "roomlistmodel.h"
#include "controller.h"
#include "neochatconfig.h"
#include "neochatroom.h"
#include "roommanager.h"
@@ -143,6 +144,7 @@ void RoomListModel::doAddRoom(Room *r)
m_rooms.append(room);
connectRoomSignals(room);
Q_EMIT roomAdded(room);
Q_EMIT Controller::instance().roomAdded(room);
} else {
qCritical() << "Attempt to add nullptr to the room list";
Q_ASSERT(false);
@@ -200,7 +202,11 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
}
Q_EMIT newHighlight(room->id(), lastEvent->id(), room->displayName(), sender->displayname(), room->eventToString(*lastEvent), room->avatar(128));
});
#ifndef QUOTIENT_07
connect(room, &Room::notificationCountChanged, this, &RoomListModel::refreshNotificationCount);
#else
connect(room, &Room::unreadStatsChanged, this, &RoomListModel::refreshNotificationCount);
#endif
}
#ifndef QUOTIENT_07
@@ -229,10 +235,13 @@ void RoomListModel::handleNotifications()
continue;
}
oldNotifications += notification["event"].toObject()["event_id"].toString();
auto room = m_connection->room(notification["room_id"].toString());
auto currentRoom = RoomManager::instance().currentRoom();
bool roomIsActive = currentRoom && room->id() == currentRoom->id();
// If room exists, room is NOT active OR the application is NOT active, show notification
if (room && !(room->id() == RoomManager::instance().currentRoom()->id() && QGuiApplication::applicationState() == Qt::ApplicationActive)) {
if (room && !(roomIsActive && QGuiApplication::applicationState() == Qt::ApplicationActive)) {
// The room might have been deleted (for example rejected invitation).
auto sender = room->user(notification["event"].toObject()["sender"].toString());

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "searchmodel.h"

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once

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