Compare commits

..

107 Commits

Author SHA1 Message Date
Soumyadeep Ghosh
0c736b7d1d snap: update libquotient 2024-12-04 01:51:31 +05:30
Joshua Goins
a2914cf7b1 Fix ShareActionStub for Windows and Android
Apparently, we are supposed to be setting source file properties for our QML files *before*
the QML module is created. Doing it after seemed to work until Qt 6.8, where it finally
broke. Notably, this makes the Android version work again but might also affect Windows.

(cherry picked from commit a39194b2ad)

Co-authored-by: Joshua Goins <josh@redstrate.com>
2024-11-22 13:44:45 +00:00
l10n daemon script
05f0277512 GIT_SILENT Sync po/docbooks with svn 2024-11-06 03:07:41 +00:00
l10n daemon script
e82a5c9442 GIT_SILENT Sync po/docbooks with svn 2024-11-02 03:06:39 +00:00
l10n daemon script
896e09bb5b GIT_SILENT Sync po/docbooks with svn 2024-11-01 03:05:02 +00:00
l10n daemon script
52747355a4 GIT_SILENT Sync po/docbooks with svn 2024-10-31 03:06:09 +00:00
l10n daemon script
01f1d655e5 GIT_SILENT made messages (after extraction) 2024-10-31 02:30:41 +00:00
Heiko Becker
4b7011fe14 GIT_SILENT Update Appstream for new release 2024-10-31 01:14:18 +01:00
Heiko Becker
5550091046 GIT_SILENT Upgrade release service version to 24.08.3. 2024-10-31 00:14:30 +01:00
l10n daemon script
fc2e751af9 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-10-30 03:02:21 +00:00
l10n daemon script
36137a036c GIT_SILENT made messages (after extraction) 2024-10-29 02:30:53 +00:00
l10n daemon script
e0f3d544f0 GIT_SILENT Sync po/docbooks with svn 2024-10-27 03:16:17 +00:00
James Graham
eb802ff91f Cherry pick compatability changes for libquotient 0.9
Title
2024-10-26 07:47:15 +00:00
l10n daemon script
0e697fc7e3 GIT_SILENT Sync po/docbooks with svn 2024-10-25 03:08:02 +00:00
Soumyadeep Ghosh
89d546f291 snap: add snap package 2024-10-18 16:40:52 +00:00
l10n daemon script
c0324d53e5 GIT_SILENT Sync po/docbooks with svn 2024-10-15 03:04:07 +00:00
l10n daemon script
25084ed46d GIT_SILENT Sync po/docbooks with svn 2024-10-11 03:18:49 +00:00
l10n daemon script
e274e09bbc SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-10-11 03:14:33 +00:00
l10n daemon script
389016f126 GIT_SILENT made messages (after extraction) 2024-10-11 02:41:20 +00:00
l10n daemon script
e270f553a3 GIT_SILENT Sync po/docbooks with svn 2024-10-10 03:04:09 +00:00
l10n daemon script
a0fef7b397 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-10-10 03:01:02 +00:00
l10n daemon script
588266d7c3 Additional po sync 2024-10-09 11:01:45 +00:00
l10n daemon script
258e799138 GIT_SILENT made messages (after extraction) 2024-10-09 02:29:41 +00:00
l10n daemon script
73cd253e25 GIT_SILENT Sync po/docbooks with svn 2024-10-07 03:04:36 +00:00
l10n daemon script
eee81329a4 GIT_SILENT made messages (after extraction) 2024-10-07 02:28:56 +00:00
Tobias Fella
9b425e5a54 Fix for not rendering html in replies
The proper fix on master branch isn't suitable to be backported
2024-10-06 22:14:22 +02:00
Tobias Fella
fb83d5cff8 Fix compilation
(cherry picked from commit a769b904dc)
2024-10-06 22:14:22 +02:00
Tobias Fella
21cabeb20c Fix compilation against changes in libQuotient
(cherry picked from commit f156551d4f)
2024-10-06 22:14:22 +02:00
Tobias Fella
bed04cc59f Adapt to libQuotient changes
(cherry picked from commit 3e1044b8fd)
2024-10-06 22:14:22 +02:00
Carl Schwan
18db04d073 Unify redaction handling
The code in messageeventmodel was calling to EventHandler::getBody who
already handles reaction handlings

(cherry picked from commit 16b27700f5)
2024-10-06 22:14:22 +02:00
Carl Schwan
0f05c1b921 Escape html from redaction message
(cherry picked from commit 9ee10b6968)
2024-10-06 22:14:14 +02:00
Heiko Becker
765292e0e9 GIT_SILENT Update Appstream for new release 2024-10-06 22:02:21 +02:00
Heiko Becker
7c27308a59 GIT_SILENT Upgrade release service version to 24.08.2. 2024-10-06 20:00:19 +02:00
l10n daemon script
b574e68164 GIT_SILENT Sync po/docbooks with svn 2024-10-06 03:06:57 +00:00
l10n daemon script
66d16dd16b SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-10-06 03:04:21 +00:00
l10n daemon script
b1c810c242 GIT_SILENT Sync po/docbooks with svn 2024-09-26 03:06:29 +00:00
l10n daemon script
8f0b3a16f1 GIT_SILENT Sync po/docbooks with svn 2024-09-24 03:32:05 +00:00
l10n daemon script
043519784b GIT_SILENT Sync po/docbooks with svn 2024-09-23 03:06:07 +00:00
Carl Schwan
aafe42c4cc Ensure floating buttons have correct size on mobile
(cherry picked from commit 42dd2e5413)

Co-authored-by: Carl Schwan <carl@carlschwan.eu>
2024-09-22 13:18:36 +00:00
Carl Schwan
d9b9b18872 Make sure that m_components cannot be accessed out of bounds in closeLinkPreview
https://crash-reports.kde.org/organizations/kde/issues/67352/?project=18&query=is%3Aunresolved+issue.priority%3A%5Bhigh%2C+medium%5D&referrer=issue-stream&statsPeriod=14d&stream_index=5


(cherry picked from commit f89cec9c55)

53f949e6 Make sure that m_components cannot be accessed out of bounds in closeLinkPreview
34b847cb Add warning

Co-authored-by: James Graham <james.h.graham@protonmail.com>
2024-09-22 13:16:39 +00:00
Carl Schwan
18e26c9e3a Fix NeoChatRoom::directChatRemoteMember
- Don't access member that's not there
- Use NeoChatRoomMember instead of RoomMember

BUG: 492733


(cherry picked from commit 2a5359e73b)

Co-authored-by: Tobias Fella <fella@posteo.de>
2024-09-22 13:14:59 +00:00
Carl Schwan
19c93e9d82 Fix crash in MessageContentModel
(cherry picked from commit 52ab6f484b)

Co-authored-by: Tobias Fella <fella@posteo.de>
2024-09-22 13:14:37 +00:00
Carl Schwan
af6187706e Fix display name in verification message
Previously this would be undefined and i18n doesn't handle this very
well


(cherry picked from commit fad4e506bc)

Co-authored-by: Carl Schwan <carl@carlschwan.eu>
2024-09-21 13:27:00 +00:00
l10n daemon script
bf18fcbbba GIT_SILENT Sync po/docbooks with svn 2024-09-19 03:08:37 +00:00
l10n daemon script
ee5502d7bf GIT_SILENT Sync po/docbooks with svn 2024-09-17 03:16:26 +00:00
l10n daemon script
d8d6a4ef3b GIT_SILENT Sync po/docbooks with svn 2024-09-16 03:30:14 +00:00
Carl Schwan
2803c6fd58 Fix filtering users in the member list
(cherry picked from commit 5c04eb85af)

Co-authored-by: Tobias Fella <fella@posteo.de>
2024-09-15 21:02:05 +00:00
Carl Schwan
f2f6406403 Don't open a room by default on mobile
Since the room window is fullscreen on mobile and you can't see the room list,
the first thing you'll be doing is backing out so you can choose the actual room you want to see


(cherry picked from commit 17da652152)

Co-authored-by: Bart Ribbers <bribbers@disroot.org>
2024-09-15 20:59:12 +00:00
Claire Elford
63dc8a5857 Fix increasing font size of certain emojis
Before this commit, NeoChat has two methods of detecting whether or not a piece
of text was an emoji. One is through a regex, and the other is by using the ICU
library. The two methods are used in different parts of the code.

This commit removes the regex detector and instead uses ICU for all the places
where NeoChat needs to figure out whether or not a string is an emoji. This
fixes increasing the font size for messages that only consist of emoji when
certain emoji are used that the regex did not handle (such as the transgender
symbol and transgender flag emojis).

(cherry picked from commit f7533a454c)
2024-09-15 22:59:54 +02:00
Carl Schwan
3dafc62f04 Use autoTransform on images
closes network/neochat#674


(cherry picked from commit 38cfc915f1)

533130b5 Use autoTransform on images

Co-authored-by: James Graham <james.h.graham@protonmail.com>
2024-09-15 10:22:08 +00:00
l10n daemon script
5be112e0e2 GIT_SILENT Sync po/docbooks with svn 2024-09-12 03:02:17 +00:00
Joshua Goins
b15ee49691 Web Shortcuts: kcmshell5 does not exist anymore on Plasma 6
This is hardcoded, but it's probably a safe assumption to think most
people running modern NeoChat are using Plasma 6 anyway.

(cherry picked from commit aa116a35f5)
2024-09-08 03:53:04 -04:00
l10n daemon script
fa5872ec5a GIT_SILENT Sync po/docbooks with svn 2024-09-08 03:08:01 +00:00
l10n daemon script
54061c744f GIT_SILENT Sync po/docbooks with svn 2024-09-07 03:06:04 +00:00
Heiko Becker
b6dac3bbdf GIT_SILENT Update Appstream for new release 2024-09-07 00:48:45 +02:00
Heiko Becker
3a838596c5 GIT_SILENT Upgrade release service version to 24.08.1. 2024-09-06 23:53:24 +02:00
l10n daemon script
cc22e80adc GIT_SILENT Sync po/docbooks with svn 2024-09-03 03:03:24 +00:00
Joshua Goins
1c0a5edd2e LocationHelper: Move clamp from zoomToFit to QML
The OSM plugin has a different zoom tolerance than what we're hardcoding
here. This fixes the map looking funky from being too zoomed while
trying to fit multiple location points at once.

(cherry picked from commit 15d6287995)
2024-09-02 09:28:15 -04:00
Joshua Goins
47408d536d AuthorDelegate: Don't make empty space clickable
(cherry picked from commit 4b7cbf37d5)
2024-09-02 09:28:15 -04:00
Joshua Goins
fc21eea7e7 AuthorDelegate: Add pointing hand cursor to indicate you can tap
It's not immediately obvious that you can press on this static text to
bring up the user's details. This isn't a problem with the avatar - for
example - because it has a pointing hand cursor. Let's do the same here.

(cherry picked from commit 0ccfe7d991)
2024-09-02 09:28:15 -04:00
Joshua Goins
ca95eb3505 Make the SectionDelegate look just a little bit better
This has insets on it - probably from qqc2-desktop-style - and makes it
look extremely bad when scrolling through messages as the background
size doesn't match. Now the insets are set to zero, except for topInset.
This is done to work around a visual bug where you can see a one-pixel
line right above the SectionDelegate when scrolling.

(cherry picked from commit ab5585cd06)
2024-09-02 09:28:15 -04:00
l10n daemon script
639cbed343 GIT_SILENT Sync po/docbooks with svn 2024-09-02 03:05:11 +00:00
l10n daemon script
be3cffaf3a GIT_SILENT Sync po/docbooks with svn 2024-09-01 03:05:11 +00:00
l10n daemon script
7af520d0ae GIT_SILENT Sync po/docbooks with svn 2024-08-30 03:10:58 +00:00
l10n daemon script
d145177014 GIT_SILENT Sync po/docbooks with svn 2024-08-28 03:30:02 +00:00
l10n daemon script
75aa2c0e8d SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-08-28 03:20:31 +00:00
l10n daemon script
7eda952aa2 GIT_SILENT Sync po/docbooks with svn 2024-08-25 03:09:56 +00:00
Tobias Fella
22d0d84fee Fix pending indicator 2024-08-24 17:42:11 +02:00
Tobias Fella
941a724381 Use plaintext for reply author component
(cherry picked from commit aeb566746a)
2024-08-24 17:33:48 +02:00
Tobias Fella
7e880c6663 Don't run QtKeychain job in a nested event loop
Doing that causes deadlocks and there's no need for it here

(cherry picked from commit 8da567d9fa)
2024-08-24 17:33:44 +02:00
Tobias Fella
22315b810c Adapt to libQuotient API change
(cherry picked from commit d99f69cc24)
2024-08-24 17:33:37 +02:00
Tobias Fella
d0c41d5224 Ensure that room list does not show rooms without title during startup
There's a brief moment during startup where the model knows about the rooms, but their state
is not loaded, which makes them show up in the room list with ugly fallback titles.
To prevent this, we delay closing the welcome page until the basestate is loaded.

(cherry picked from commit bd1d4289c0)
2024-08-24 17:33:30 +02:00
Andreas Sturmlechner
a7e06375fd Include missing ECMQmlModule
Amends bc67033c00 and e0c3a1c143

No idea why this isn't caught by CI, but it fails for me otherwise.

Signed-off-by: Andreas Sturmlechner <asturm@gentoo.org>
(cherry picked from commit 22743b6d8b)
2024-08-24 12:24:15 +02:00
l10n daemon script
e638f61ff1 GIT_SILENT Sync po/docbooks with svn 2024-08-22 03:20:42 +00:00
l10n daemon script
2218b39a50 GIT_SILENT Sync po/docbooks with svn 2024-08-20 03:27:56 +00:00
Tobias Fella
b34936a017 Re-add pending event indicator
This seems to have been accidentally removed

BUG: 491277
(cherry picked from commit 776807580a)
2024-08-18 15:37:08 +02:00
l10n daemon script
5b3e650854 GIT_SILENT Sync po/docbooks with svn 2024-08-16 03:04:39 +00:00
Heiko Becker
86d85c6ce7 GIT_SILENT Update Appstream for new release 2024-08-16 00:35:00 +02:00
Heiko Becker
863e20394a GIT_SILENT Upgrade release service version to 24.08.0. 2024-08-15 23:34:44 +02:00
l10n daemon script
908dcea75e GIT_SILENT made messages (after extraction) 2024-08-11 02:34:30 +00:00
l10n daemon script
c59d5bc75d SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-08-10 03:12:41 +00:00
l10n daemon script
ff4ba4df9c GIT_SILENT made messages (after extraction) 2024-08-10 02:39:29 +00:00
l10n daemon script
0c1494e74a SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-08-09 03:01:15 +00:00
Albert Astals Cid
18d4da8b42 GIT_SILENT Upgrade release service version to 24.07.90. 2024-08-08 10:00:41 +02:00
l10n daemon script
9680cbbb38 GIT_SILENT Sync po/docbooks with svn 2024-08-06 03:07:30 +00:00
l10n daemon script
3b5ba470b6 GIT_SILENT Sync po/docbooks with svn 2024-08-04 03:12:15 +00:00
l10n daemon script
e1066aede2 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-08-04 03:08:29 +00:00
l10n daemon script
2bf4ed26f0 GIT_SILENT Sync po/docbooks with svn 2024-08-03 03:09:58 +00:00
l10n daemon script
b460b588a8 GIT_SILENT Sync po/docbooks with svn 2024-08-02 03:10:44 +00:00
l10n daemon script
05270c38bf GIT_SILENT Sync po/docbooks with svn 2024-07-31 03:05:10 +00:00
l10n daemon script
18afad83db GIT_SILENT Sync po/docbooks with svn 2024-07-30 03:09:21 +00:00
Tobias Fella
d390433b2b Show time without seconds in timeline
(cherry picked from commit 2df2e39d43)
2024-07-29 18:00:10 +02:00
Tobias Fella
0017be1c0f Show time without timezone in tooltip
Constructing the timezone string is relatively heavy and is done for each delegate, even when the tooltip is never shown

(cherry picked from commit 42cec7d5ba)
2024-07-29 18:00:08 +02:00
l10n daemon script
63eda3796d GIT_SILENT Sync po/docbooks with svn 2024-07-29 03:09:38 +00:00
l10n daemon script
3246076a0b GIT_SILENT Sync po/docbooks with svn 2024-07-28 03:10:41 +00:00
James Graham
e905cdd151 Cherrypick: Create NeochatRoomMember as a shim for RoomMember
The intention is that NeochatRoomMember can be created passed to QML and then be fully managed by it. It effectively just grabs the current RoomMember, calls the correct function then discards it so that we don't end up trying to access an already deleted state event.


(cherry picked from commit 11fd4f88ec)

a2a8ad09 Create NeochatRoomMember as a shim for RoomMember so it can be safely passed to QML
0867eef5 Fix showAuthor
0f72ccd0 Mamange the creation of NeochatRoomMembers and only create one per member rather than event.
dba88fe2 REmove getAuthor as no longer needed
4e3a61d1 Update include
32d4d9f7 Pass NeochatRoomMembers rather than RoomMembers to menus
8e4b2034 Don't leak memory
c2f2bb26 Fix code component regression.
5aee89be Make sure the sender Id is intialised properly for pending events
c10c2677 Tweak intialisation
b3146034 Make sure event objects are created for new pending events
e4fab6d9 Pass an empty NeochatRoomMember when not in the map

Co-authored-by: James Graham <james.h.graham@protonmail.com>
2024-07-27 09:33:14 +00:00
l10n daemon script
0372074beb GIT_SILENT Sync po/docbooks with svn 2024-07-27 03:10:43 +00:00
Joshua Goins
09e97f2bdb Make the "Unignore this user" button work on the Ignored Users page
This function now takes a QString. This should be fine to use since it's
in the 0.8.2 release which we require.

(cherry picked from commit 9a4504ce61)
2024-07-26 19:09:57 +00:00
Joshua Goins
8e324c16f3 Fix the emoji page not doing anything
This is yet more stuff broken due to referencing a implicit pageStack
that no longer exists.


(cherry picked from commit cb8ed02e82)

Co-authored-by: Joshua Goins <josh@redstrate.com>
2024-07-26 19:00:47 +00:00
Joshua Goins
2bb55eece7 Don't flag invite notifications as persistent
These really don't need to be persistent, as they even stick around
when NeoChat is closed. This also spams the user's notification system
usually, if they get lots of invitations at once which don't go away
automatically.

(cherry picked from commit 51197d7c1a)
2024-07-25 16:38:13 -04:00
Joshua Goins
2877e40647 Hint that the user can auto-reject invitations on the invite page
This should help make the new setting more discoverable.

(cherry picked from commit 2a2a2e0c05)
2024-07-25 16:38:13 -04:00
Joshua Goins
768ec242fa Allow blocking invites from people you don't share a room with
Matrix currently has a significant moderation loophole, thanks to
invites. Right now, anyone can invite anyone to a room - and clients
like NeoChat will gladly display these rooms to them and even give you
a notification.

However, this creates a pretty easy attack since room names and avatars
are arbitrary and this is a known vector of harassment in the Matrix
community. There's currently no tools to block this server-side, so
let's try to improve the situation where we can.

This adds a new setting to the Security page, wherein it allows you to
block invites from people you don't share a room with. This prevents the
notification from appearing and NeoChat will attempt to leave the room
immediately.

Since this depends on MSC 2666 - a currently unstable feature - the
server may not support it and NeoChat will disable the setting in this
case.

(cherry picked from commit 07fee30cc0)
2024-07-25 16:38:13 -04:00
Joshua Goins
194751627f Don't display notifications for invite rooms
Sometimes a ghost notification will appear, this is sometimes a stray
m.room.invite notification we didn't handle or some other event. Let's
outright reject all notifications from Invite-type rooms to prevent this
from happening altogether.

(cherry picked from commit 83c6ce0ace)
2024-07-25 16:38:13 -04:00
l10n daemon script
bc701c51d9 GIT_SILENT Sync po/docbooks with svn 2024-07-25 03:13:15 +00:00
l10n daemon script
35939b4af4 GIT_SILENT Sync po/docbooks with svn 2024-07-23 03:14:18 +00:00
l10n daemon script
a178b8b6ca SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-07-23 03:04:52 +00:00
Albert Astals Cid
a6994318de GIT_SILENT Upgrade release service version to 24.07.80. 2024-07-21 12:25:07 +02:00
624 changed files with 175349 additions and 261582 deletions

View File

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

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat", "id": "org.kde.neochat",
"branch": "master", "branch": "master",
"runtime": "org.kde.Platform", "runtime": "org.kde.Platform",
"runtime-version": "6.9", "runtime-version": "6.7",
"sdk": "org.kde.Sdk", "sdk": "org.kde.Sdk",
"command": "neochat", "command": "neochat",
"tags": [ "tags": [
@@ -25,23 +25,13 @@
"modules": [ "modules": [
{ {
"name": "kirigamiaddons", "name": "kirigamiaddons",
"config-opts": [ "config-opts": [ "-DBUILD_TESTING=OFF" ],
"-DBUILD_TESTING=OFF"
],
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"sources": [ "sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kirigami-addons.git", "commit": "34d311219e8b7209746a98b3a29b91ded05ff936" } ]
{
"type": "git",
"url": "https://invent.kde.org/libraries/kirigami-addons.git"
}
]
}, },
{ {
"name": "kquickimageeditor", "name": "kquickimageeditor",
"config-opts": [ "config-opts": [ "-DBUILD_WITH_QT6=ON" ],
"-DBUILD_WITH_QT6=ON",
"-DBUILD_TESTING=OFF"
],
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"sources": [ "sources": [
{ {
@@ -53,19 +43,17 @@
{ {
"name": "olm", "name": "olm",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"config-opts": [ "config-opts": [ "-DOLM_TESTS=OFF" ],
"-DOLM_TESTS=OFF"
],
"sources": [ "sources": [
{ {
"type": "git", "type": "git",
"url": "https://gitlab.matrix.org/matrix-org/olm.git", "url": "https://gitlab.matrix.org/matrix-org/olm.git",
"tag": "3.2.16", "tag": "3.2.10",
"x-checker-data": { "x-checker-data": {
"type": "git", "type": "git",
"tag-pattern": "^([\\d.]+)$" "tag-pattern": "^([\\d.]+)$"
}, },
"commit": "7e0c8277032e40308987257b711b38af8d77cc69" "commit": "9908862979147a71dc6abaecd521be526ae77be1"
} }
] ]
}, },
@@ -77,13 +65,13 @@
"-Dvapi=false", "-Dvapi=false",
"-Dgtk_doc=false", "-Dgtk_doc=false",
"-Dintrospection=false", "-Dintrospection=false",
"-Dcrypto=disabled" "-Dgcrypt=false"
], ],
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.6.tar.xz", "url": "https://download.gnome.org/sources/libsecret/0.20/libsecret-0.20.5.tar.xz",
"sha256": "747b8c175be108c880d3adfb9c3537ea66e520e4ad2dccf5dce58003aeeca090", "sha256": "3fb3ce340fcd7db54d87c893e69bfc2b1f6e4d4b279065ffe66dac9f0fd12b4d",
"x-checker-data": { "x-checker-data": {
"type": "gnome", "type": "gnome",
"name": "libsecret", "name": "libsecret",
@@ -98,13 +86,13 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/0.15.0.tar.gz", "url": "https://github.com/frankosterfeld/qtkeychain/archive/0.14.2.tar.gz",
"sha256": "f4254dc8f0933b06d90672d683eab08ef770acd8336e44dfa030ce041dc2ca22", "sha256": "cf2e972b783ba66334a79a30f6b3a1ea794a1dc574d6c3bebae5ffd2f0399571",
"x-checker-data": { "x-checker-data": {
"type": "anitya", "type": "anitya",
"project-id": 4138, "project-id": 4138,
"stable-only": true, "stable-only": true,
"url-template": "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/$version.tar.gz" "url-template": "https://github.com/frankosterfeld/qtkeychain/archive/v$version.tar.gz"
} }
} }
], ],
@@ -112,8 +100,7 @@
"-DBUILD_WITH_QT6=ON", "-DBUILD_WITH_QT6=ON",
"-DCMAKE_INSTALL_LIBDIR=/app/lib", "-DCMAKE_INSTALL_LIBDIR=/app/lib",
"-DLIB_INSTALL_DIR=/app/lib", "-DLIB_INSTALL_DIR=/app/lib",
"-DBUILD_TRANSLATIONS=NO", "-DBUILD_TRANSLATIONS=NO"
"-DBUILD_TESTING=OFF"
] ]
}, },
{ {
@@ -123,7 +110,7 @@
{ {
"type": "git", "type": "git",
"url": "https://github.com/quotient-im/libQuotient.git", "url": "https://github.com/quotient-im/libQuotient.git",
"branch": "dev", "branch": "0.8.x",
"disable-submodules": true "disable-submodules": true
} }
], ],
@@ -136,33 +123,34 @@
{ {
"name": "cmark", "name": "cmark",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"config-opts": [ "config-opts": [ "-DCMARK_TESTS=OFF" ],
"-DCMARK_TESTS=OFF",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=/app"
],
"sources": [ "sources": [
{ {
"type": "git", "type": "git",
"url": "https://github.com/commonmark/cmark.git" "url": "https://github.com/commonmark/cmark.git"
} }
], ],
"config-opts": [
"-DCMARK_TESTS=OFF",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=/app"
],
"builddir": true "builddir": true
}, },
{ {
"name": "kunifiedpush", "name": "qcoro",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"builddir": true, "config-opts": [ "-DQCORO_BUILD_EXAMPLES=OFF", "-DBUILD_TESTING=OFF" ],
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-1.0.0.tar.xz", "url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.7.0.tar.gz",
"sha256": "2ddeba21306d0307114ec50a2c38159ec62359f9fc6cdd58da30a369fbd550cf", "sha256": "23ef0217926e67c8d2eb861cf91617da2f7d8d5a9ae6c62321b21448b1669210",
"x-checker-data": { "x-checker-data": {
"type": "anitya", "type": "anitya",
"project-id": 375055, "project-id": 236236,
"stable-only": true, "stable-only": true,
"url-template": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-$version.tar.xz" "url-template": "https://github.com/danvratil/qcoro/archive/refs/tags/v$version.tar.gz"
} }
} }
] ]
@@ -170,15 +158,14 @@
{ {
"name": "neochat", "name": "neochat",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"config-opts": [
"-DBUILD_TESTING=OFF",
"-DNEOCHAT_FLATPAK=ON"
],
"sources": [ "sources": [
{ {
"type": "dir", "type": "dir",
"path": "." "path": "."
} }
],
"config-opts": [
"-DNEOCHAT_FLATPAK=ON"
] ]
} }
] ]

View File

@@ -5,17 +5,11 @@ include:
- project: sysadmin/ci-utilities - project: sysadmin/ci-utilities
file: file:
- /gitlab-templates/reuse-lint.yml - /gitlab-templates/reuse-lint.yml
- /gitlab-templates/json-validation.yml
- /gitlab-templates/xml-lint.yml
- /gitlab-templates/yaml-lint.yml
- /gitlab-templates/android-qt6.yml - /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml - /gitlab-templates/linux-qt6.yml
- /gitlab-templates/linux-qt6-next.yml
- /gitlab-templates/windows-qt6.yml - /gitlab-templates/windows-qt6.yml
- /gitlab-templates/freebsd-qt6.yml # - /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml - /gitlab-templates/flatpak.yml
- /gitlab-templates/snap-snapcraft-lxd.yml
- /gitlab-templates/craft-android-qt6-apks.yml - /gitlab-templates/craft-android-qt6-apks.yml
- /gitlab-templates/craft-appimage-qt6.yml - /gitlab-templates/craft-appimage-qt6.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml - /gitlab-templates/craft-windows-x86-64-qt6.yml
- /gitlab-templates/craft-windows-appx-qt6.yml

View File

@@ -2,44 +2,42 @@
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
Dependencies: Dependencies:
- 'on': ['Linux', 'Android', 'FreeBSD', 'Windows'] - 'on': ['Linux', 'Android', 'FreeBSD', 'Windows']
'require': 'require':
'frameworks/extra-cmake-modules': '@latest-kf6' 'frameworks/extra-cmake-modules': '@latest-kf6'
'frameworks/kcoreaddons': '@latest-kf6' 'frameworks/kcoreaddons': '@latest-kf6'
'frameworks/kirigami': '@latest-kf6' 'frameworks/kirigami': '@latest-kf6'
'frameworks/ki18n': '@latest-kf6' 'frameworks/ki18n': '@latest-kf6'
'frameworks/kconfig': '@latest-kf6' 'frameworks/kconfig': '@latest-kf6'
'frameworks/syntax-highlighting': '@latest-kf6' 'frameworks/syntax-highlighting': '@latest-kf6'
'frameworks/kitemmodels': '@latest-kf6' 'frameworks/kitemmodels': '@latest-kf6'
'frameworks/kquickcharts': '@latest-kf6' 'frameworks/kquickcharts': '@latest-kf6'
'frameworks/knotifications': '@latest-kf6' 'frameworks/knotifications': '@latest-kf6'
'frameworks/kcolorscheme': '@latest-kf6' 'frameworks/kcolorscheme': '@latest-kf6'
'frameworks/kiconthemes': '@latest-kf6' 'libraries/kquickimageeditor': '@latest-kf6'
'libraries/kquickimageeditor': '@latest-kf6' 'frameworks/sonnet': '@latest-kf6'
'frameworks/sonnet': '@latest-kf6' 'frameworks/prison': '@latest-kf6'
'frameworks/prison': '@latest-kf6' 'libraries/kirigami-addons': '@latest-kf6'
'libraries/kirigami-addons': '@latest-kf6' 'third-party/libquotient': '@latest'
'third-party/libquotient': '@latest' 'third-party/qtkeychain': '@latest'
'third-party/qtkeychain': '@latest' 'third-party/cmark': '@latest'
'third-party/cmark': '@latest' 'third-party/qcoro': '@latest'
'third-party/qcoro': '@latest' - 'on': ['Windows', 'Linux', 'FreeBSD']
- 'on': ['Windows', 'Linux', 'FreeBSD'] 'require':
'require': 'frameworks/qqc2-desktop-style': '@latest-kf6'
'frameworks/qqc2-desktop-style': '@latest-kf6' 'frameworks/kio': '@latest-kf6'
'frameworks/kio': '@latest-kf6' 'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6' 'frameworks/kstatusnotifieritem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6' 'frameworks/kcrash': '@latest-kf6'
'frameworks/kcrash': '@latest-kf6' - 'on': ['Linux', 'FreeBSD']
- 'on': ['Linux', 'FreeBSD'] 'require':
'require': 'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/kdbusaddons': '@latest-kf6' 'frameworks/purpose': '@latest-kf6'
'frameworks/purpose': '@latest-kf6'
'libraries/kunifiedpush': '@latest-kf6'
- 'on': ['Linux'] - 'on': ['Linux']
'require': 'require':
'sdk/selenium-webdriver-at-spi': '@latest-kf6' 'sdk/selenium-webdriver-at-spi': '@latest-kf6'
Options: Options:
per-test-timeout: 90 per-test-timeout: 90
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows'] require-passing-tests-on: [ 'Linux', 'Android', 'FreeBSD' ]

55
.reuse/dep5 Normal file
View File

@@ -0,0 +1,55 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: NeoChat
Upstream-Contact: Carl Schwan <carlschwan@kde.org>
Files: 128-logo.png icons/* logo.png org.kde.neochat.svg org.kde.neochat.tray.svg android/res/drawable/neochat.png
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 <tobias.fella@kde.org>
License: BSD-2-Clause
Files: .gitignore
Copyright: None
License: CC0-1.0
Files: .gitlab/issue_templates/bug.md
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: CC0-1.0
Files: src/res.qrc src/res_android.qrc src/res_desktop.qrc
Copyright: None
License: CC0-1.0
Files: cmake/Flatpak/99-noto-mono-color-emoji.conf
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 <tobias.fella@kde.org>
License: BSD-2-Clause
Files: src/neochat.notifyrc
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: src/qml/confetti.png src/qml/glowdot.png
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
License: CC0-1.0
Files: .flatpak-manifest.json
Copyright: 2020-2022 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: autotests/data/*
Copyright: none
License: CC0-1.0
Files: appiumtests/data/*
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: CC0-1.0
Files: src/purpose/purposeplugin.json
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause

View File

@@ -7,14 +7,14 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script. # KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "25") set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "08") set(RELEASE_SERVICE_VERSION_MINOR "08")
set(RELEASE_SERVICE_VERSION_MICRO "3") set(RELEASE_SERVICE_VERSION_MICRO "3")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION}) project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
set(KF_MIN_VERSION "6.16") set(KF_MIN_VERSION "6.0")
set(QT_MIN_VERSION "6.5") set(QT_MIN_VERSION "6.5")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE) find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
@@ -39,8 +39,6 @@ include(ECMCheckOutboundLicense)
include(ECMQtDeclareLoggingCategory) include(ECMQtDeclareLoggingCategory)
include(ECMAddAndroidApk) include(ECMAddAndroidApk)
include(ECMQmlModule) include(ECMQmlModule)
include(GenerateExportHeader)
include(ECMGenerateHeaders)
if (NOT ANDROID) if (NOT ANDROID)
include(KDEClangFormat) include(KDEClangFormat)
endif() endif()
@@ -56,7 +54,7 @@ ecm_setup_version(${PROJECT_VERSION}
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
) )
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg TextToSpeech WebView) find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg WebView)
set_package_properties(Qt6 PROPERTIES set_package_properties(Qt6 PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Basic application components" PURPOSE "Basic application components"
@@ -66,7 +64,7 @@ if (QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW) qt_policy(SET QTP0004 NEW)
endif () endif ()
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels IconThemes ColorScheme) find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
set_package_properties(KF6 PROPERTIES set_package_properties(KF6 PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Basic application components" PURPOSE "Basic application components"
@@ -75,7 +73,7 @@ set_package_properties(KF6Kirigami PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Kirigami application UI framework" PURPOSE "Kirigami application UI framework"
) )
find_package(KF6KirigamiAddons 1.6.0 REQUIRED) find_package(KF6KirigamiAddons 0.7.2 REQUIRED)
if (UNIX AND NOT APPLE AND NOT ANDROID AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE) if (UNIX AND NOT APPLE AND NOT ANDROID AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Purpose) find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Purpose)
@@ -103,11 +101,11 @@ else()
) )
endif() endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU) if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED) find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
endif() endif()
find_package(QuotientQt6 0.9.1) find_package(QuotientQt6 0.8.2)
set_package_properties(QuotientQt6 PROPERTIES set_package_properties(QuotientQt6 PROPERTIES
TYPE REQUIRED TYPE REQUIRED
DESCRIPTION "Qt wrapper around Matrix API" DESCRIPTION "Qt wrapper around Matrix API"
@@ -115,6 +113,11 @@ set_package_properties(QuotientQt6 PROPERTIES
PURPOSE "Talk with matrix server" PURPOSE "Talk with matrix server"
) )
if (NOT TARGET Olm::Olm)
message(FATAL_ERROR "NeoChat requires Quotient with the E2EE feature enabled")
endif()
find_package(cmark) find_package(cmark)
set_package_properties(cmark PROPERTIES set_package_properties(cmark PROPERTIES
TYPE REQUIRED TYPE REQUIRED
@@ -147,24 +150,16 @@ set_package_properties(KF6DocTools PROPERTIES DESCRIPTION
TYPE OPTIONAL TYPE OPTIONAL
) )
option(WITH_UNIFIEDPUSH "Build with KUnifiedPush support" ON) find_package(KUnifiedPush QUIET)
set_package_properties(KUnifiedPush PROPERTIES
if (ANDROID OR APPLE OR WIN32 OR HAIKU) TYPE OPTIONAL
set(WITH_UNIFIEDPUSH OFF) PURPOSE "Push notification support"
endif() URL "https://invent.kde.org/libraries/kunifiedpush"
)
if (WITH_UNIFIEDPUSH)
find_package(KUnifiedPush)
set_package_properties(KUnifiedPush PROPERTIES
TYPE REQUIRED
PURPOSE "Push notification support"
URL "https://invent.kde.org/libraries/kunifiedpush"
)
endif()
if(ANDROID) if(ANDROID)
find_package(Sqlite3) find_package(Sqlite3)
set(BUILD_TESTING FALSE) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
endif() endif()
ki18n_install(po) ki18n_install(po)
@@ -179,12 +174,9 @@ add_definitions(-DQT_NO_FOREACH)
add_subdirectory(src) add_subdirectory(src)
if (BUILD_TESTING) if (BUILD_TESTING)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test HttpServer) find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
add_subdirectory(autotests) add_subdirectory(autotests)
# add_subdirectory(appiumtests) add_subdirectory(appiumtests)
if (NOT ANDROID)
add_subdirectory(memorytests)
endif()
endif() endif()
if(KF6DocTools_FOUND) if(KF6DocTools_FOUND)
@@ -192,7 +184,7 @@ if(KF6DocTools_FOUND)
add_subdirectory(doc) add_subdirectory(doc)
endif() endif()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
if (NOT ANDROID) if (NOT ANDROID)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h) file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)

View File

@@ -11,7 +11,7 @@ A Qt/QML based Matrix client.
<a href='https://matrix.org'><img src='https://matrix.org/docs/legacy/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a> <a href='https://matrix.org'><img src='https://matrix.org/docs/legacy/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a> <a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
<a href='https://snapcraft.io/neochat'><img width='190px' alt='Download on the Snap Store' src='https://apps.kde.org/store_badges/snapstore/en.svg'/></a> <a href='https://snapcraft.io/neochat'><img width='190px' alt='Download on the Snap Store' src='https://snapcraft.io/static/images/badges/en/snap-store-black.svg'/></a>
## Introduction ## Introduction

View File

@@ -1,90 +0,0 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: CC0-1.0
version = 1
SPDX-PackageName = "NeoChat"
SPDX-PackageSupplier = "Carl Schwan <carlschwan@kde.org>"
[[annotations]]
path = ["128-logo.png", "icons/**", "logo.png", "org.kde.neochat.svg", "org.kde.neochat.tray.svg", "android/res/drawable/neochat.png", "android/neochat-playstore.png"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Carson Black <uhhadd@gmail.com>"
SPDX-License-Identifier = "LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL"
[[annotations]]
path = "android/res/drawable/splash.xml"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = ".gitignore"
precedence = "aggregate"
SPDX-FileCopyrightText = "None"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ".gitlab/issue_templates/bug.md"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Carl Schwan <carlschwan@kde.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ["src/res.qrc", "src/res_android.qrc", "src/res_desktop.qrc"]
precedence = "aggregate"
SPDX-FileCopyrightText = "None"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "cmake/Flatpak/99-noto-mono-color-emoji.conf"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Carl Schwan <carlschwan@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "src/app/neochatconfig.kcfg"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "src/app/neochat.notifyrc"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = ["src/qml/confetti.png", "src/qml/glowdot.png"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Alexey Andreyev <aa13q@ya.ru>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ".flatpak-manifest.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020-2022 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "autotests/data/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "none"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "appiumtests/data/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "src/purpose/purposeplugin.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "memorytests/memtest-sync.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2024 James Graham <james.h.graham@protonmail.com>"
SPDX-License-Identifier = "BSD-2-Clause"

View File

@@ -55,6 +55,5 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <!-- for Android >= 33 -->
</manifest> </manifest>

View File

@@ -12,7 +12,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:8.6.0' classpath 'com.android.tools.build:gradle:7.4.1'
} }
} }
@@ -23,7 +23,8 @@ repositories {
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply from: '../ecm-version.gradle' apply from: '../version.gradle'
def timestamp = (int)(new Date().getTime()/1000)
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
@@ -78,9 +79,9 @@ android {
targetSdkVersion qtTargetSdkVersion targetSdkVersion qtTargetSdkVersion
applicationId "org.kde.neochat" applicationId "org.kde.neochat"
namespace "org.kde.neochat" namespace "org.kde.neochat"
versionCode ecmVersionCode versionCode timestamp
versionName ecmVersionName versionName projectVersionFull
manifestPlaceholders = [versionName: ecmVersionName, versionCode: ecmVersionCode] manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
} }
packagingOptions { packagingOptions {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
// SPDX-License-Identifier: BSD-3-Clause
ext {
projectVersionFull = "@NEOCHAT_VERSION@"
}

View File

@@ -5,26 +5,6 @@
"!room_id_1234:localhost:1234": { "!room_id_1234:localhost:1234": {
"state": { "state": {
"events": [ "events": [
{
"content": {
"m.federate": true,
"predecessor": {
"event_id": "$something:example.org",
"room_id": "!oldroom:example.org"
},
"room_version": "11"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "",
"type": "m.room.create",
"unsigned": {
"age": 1234,
"membership": "join"
}
},
{ {
"type": "m.room.member", "type": "m.room.member",
"state_key": "@user:localhost:1234", "state_key": "@user:localhost:1234",
@@ -46,26 +26,6 @@
}, },
"timeline": { "timeline": {
"events": [ "events": [
{
"content": {
"m.federate": true,
"predecessor": {
"event_id": "$something:example.org",
"room_id": "!oldroom:example.org"
},
"room_version": "11"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "",
"type": "m.room.create",
"unsigned": {
"age": 1234,
"membership": "join"
}
},
{ {
"type": "m.room.message", "type": "m.room.message",
"sender": "@user:localhost:1234", "sender": "@user:localhost:1234",

View File

@@ -83,15 +83,6 @@ def create_room():
next_sync_payload = "sync_response_new_room" next_sync_payload = "sync_response_new_room"
return response return response
@app.route("/_matrix/client/v3/publicRooms", methods=["POST"])
def public_rooms():
if request.get_json()["filter"]["generic_search_term"] == "forbidden":
data = dict()
data["errcode"] = "M_FORBIDDEN"
data["error"] = "You are not allowed to search for this. Go to https://wikipedia.org for more information"
return data, 403
return dict()
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -3,10 +3,6 @@
enable_testing() enable_testing()
add_library(neochat_server STATIC server.cpp)
target_link_libraries(neochat_server PUBLIC Qt::HttpServer QuotientQt6)
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" ) add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
ecm_add_test( ecm_add_test(
@@ -52,9 +48,15 @@ ecm_add_test(
) )
ecm_add_test( ecm_add_test(
timelinemessagemodeltest.cpp messageeventmodeltest.cpp
LINK_LIBRARIES neochat Qt::Test LINK_LIBRARIES neochat Qt::Test
TEST_NAME timelinemessagemodeltest TEST_NAME messageeventmodeltest
)
ecm_add_test(
actionshandlertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME actionshandlertest
) )
ecm_add_test( ecm_add_test(
@@ -80,15 +82,3 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test LINK_LIBRARIES neochat Qt::Test
TEST_NAME linkpreviewertest TEST_NAME linkpreviewertest
) )
ecm_add_test(
messagecontentmodeltest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME messagecontentmodeltest
)
ecm_add_test(
actionstest.cpp
LINK_LIBRARIES neochat Qt::Test neochat_server
TEST_NAME actionstest
)

View File

@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QTest>
#include "actionshandler.h"
#include "chatbarcache.h"
#include "testutils.h"
class ActionsHandlerTest : public QObject
{
Q_OBJECT
private:
Quotient::Connection *connection = Quotient::Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
ActionsHandler *actionsHandler = new ActionsHandler(this);
private Q_SLOTS:
void nullObject();
};
void ActionsHandlerTest::nullObject()
{
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
actionsHandler->handleMessageEvent(nullptr);
auto chatBarCache = new ChatBarCache(this);
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
actionsHandler->handleMessageEvent(chatBarCache);
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
actionsHandler->setRoom(room);
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
actionsHandler->handleMessageEvent(nullptr);
// The final one should throw no warning so we make sure.
QTest::failOnWarning("ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
actionsHandler->handleMessageEvent(chatBarCache);
}
QTEST_GUILESS_MAIN(ActionsHandlerTest)
#include "actionshandlertest.moc"

View File

@@ -1,151 +0,0 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject>
#include <QTest>
#include <QSignalSpy>
#include <QVariantList>
#include "accountmanager.h"
#include "chatbarcache.h"
#include "models/actionsmodel.h"
#include "server.h"
#include "testutils.h"
using namespace Quotient;
//TODO: rainbow, rainbowme, plain, spoiler, me, join, knock, j, part, leave, nick, roomnick, myroomnick, ignore, unignore, react, ban, unban, kick
class ActionsTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
NeoChatRoom *room = nullptr;
void expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message);
Server server;
private Q_SLOTS:
void initTestCase();
void testActions();
void testActions_data();
void testInvite();
};
void ActionsTest::initTestCase()
{
Connection::setRoomType<NeoChatRoom>();
server.start();
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
auto accountManager = new AccountManager(true);
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
connection = accountManager->accounts()->front();
auto roomId = server.createRoom(u"@user:localhost:1234"_s);
server.inviteUser(roomId, u"@invited:example.com"_s);
server.banUser(roomId, u"@banned:example.com"_s);
server.joinUser(roomId, u"@example:example.com"_s);
QSignalSpy syncSpy(connection, &Connection::syncDone);
// We need to wait for two syncs, as the next one won't have the changes yet
QVERIFY(syncSpy.wait());
QVERIFY(syncSpy.wait());
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
QVERIFY(room);
}
void ActionsTest::testActions_data()
{
QTest::addColumn<QString>("command");
QTest::addColumn<std::optional<QString>>("resultText");
QTest::addColumn<std::optional<Quotient::RoomMessageEvent::MsgType>>("type");
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\\\_(ツ)\\_/¯ Hello"_s)
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
QTest::newRow("lenny") << u"/lenny Hello"_s << std::make_optional(u"( ͡° ͜ʖ ͡°) Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
QTest::newRow("tableflip") << u"/tableflip Hello"_s << std::make_optional(u"(╯°□°)╯︵ ┻━┻ Hello"_s)
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
QTest::newRow("unflip") << u"/unflip Hello"_s << std::make_optional(u"┬──┬ ( ゜-゜ノ) Hello"_s)
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
QTest::newRow("rainbow") << u"/rainbow Hello"_s << std::optional<QString>() << std::optional<Quotient::RoomMessageEvent::MsgType>();
QTest::newRow("rainbowme") << u"/rainbowme Hello"_s << std::optional<QString>() << std::optional<Quotient::RoomMessageEvent::MsgType>();
QTest::newRow("plain") << u"/plain <b>Hello</b>"_s << std::optional<QString>() << std::optional<Quotient::RoomMessageEvent::MsgType>();
QTest::newRow("spoiler") << u"/spoiler Hello"_s << std::optional<QString>() << std::optional<Quotient::RoomMessageEvent::MsgType>();
QTest::newRow("me") << u"/me Hello"_s << std::make_optional(u"Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Emote);
QTest::newRow("notice") << u"/notice Hello"_s << std::make_optional(u"Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Notice);
QTest::newRow("message") << u"Hello"_s << std::make_optional(u"Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
QTest::newRow("invite") << u"/invite @foo:example.com"_s << std::optional<QString>() << std::optional<Quotient::RoomMessageEvent::MsgType>();
//TODO: join, knock, j, part, leave, nick, roomnick, myroomnick, ignore, unignore, react, ban, unban, kick
}
void ActionsTest::testActions()
{
QFETCH(QString, command);
QFETCH(std::optional<QString>, resultText);
QFETCH(std::optional<Quotient::RoomMessageEvent::MsgType>, type);
auto cache = new ChatBarCache();
cache->setText(command);
auto result = ActionsModel::handleAction(room, cache);
QCOMPARE(resultText, std::get<std::optional<QString>>(result));
QCOMPARE(type, std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result));
}
static ActionsModel::Action findAction(const QString &name)
{
for (const auto &action : ActionsModel::allActions()) {
if (action.prefix == name) {
return action;
}
}
return {};
}
void ActionsTest::expectMessage(const QString &actionName, const QString &args, MessageType::Type type, const QString &message)
{
auto action = findAction(actionName);
QSignalSpy spy(room, &NeoChatRoom::showMessage);
auto result = action.handle(args, room, nullptr);
auto expected = QVariantList {type, message};
auto signal = spy.takeFirst();
QCOMPARE(signal, expected);
}
void ActionsTest::testInvite()
{
expectMessage(u"invite"_s, u"foo"_s, MessageType::Error, u"'foo' does not look like a matrix id."_s);
expectMessage(u"invite"_s, u"@invited:example.com"_s, MessageType::Information, u"@invited:example.com is already invited to this room."_s);
QCOMPARE(room->memberState(u"@invited:example.com"_s), Membership::Invite);
expectMessage(u"invite"_s, u"@banned:example.com"_s, MessageType::Information, u"@banned:example.com is banned from this room."_s);
QCOMPARE(room->memberState(u"@banned:example.com"_s), Membership::Ban);
expectMessage(u"invite"_s, connection->userId(), MessageType::Positive, u"You are already in this room."_s);
QCOMPARE(room->memberState(connection->userId()), Membership::Join);
expectMessage(u"invite"_s, u"@example:example.com"_s, MessageType::Information, u"@example:example.com is already in this room."_s);
QCOMPARE(room->memberState(u"@example:example.com"_s), Membership::Join);
QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Leave);
expectMessage(u"invite"_s, u"@user:example.com"_s, MessageType::Positive, u"@user:example.com was invited into this room."_s);
QSignalSpy spy(room, &NeoChatRoom::changed);
QVERIFY(spy.wait());
auto tries = 0;
while (room->memberState(u"@user:example.com"_s) != Membership::Invite) {
QVERIFY(spy.wait());
tries += 1;
if (tries > 3) {
QVERIFY(false);
}
}
QCOMPARE(room->memberState(u"@user:example.com"_s), Membership::Invite);
}
QTEST_MAIN(ActionsTest)
#include "actionstest.moc"

View File

@@ -38,8 +38,8 @@ private Q_SLOTS:
void ChatBarCacheTest::initTestCase() void ChatBarCacheTest::initTestCase()
{ {
connection = Connection::makeMockConnection(u"@bob:kde.org"_s); connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, "test-min-sync.json"_L1); room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-min-sync.json"));
} }
void ChatBarCacheTest::empty() void ChatBarCacheTest::empty()
@@ -51,7 +51,7 @@ void ChatBarCacheTest::empty()
QCOMPARE(chatBarCache->replyId(), QString()); QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false); QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString()); QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationAuthor(), room->member(QString())); QCOMPARE(chatBarCache->relationUser(), room->member(QString()));
QCOMPARE(chatBarCache->relationMessage(), QString()); QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QString()); QCOMPARE(chatBarCache->attachmentPath(), QString());
} }
@@ -59,13 +59,13 @@ void ChatBarCacheTest::empty()
void ChatBarCacheTest::noRoom() void ChatBarCacheTest::noRoom()
{ {
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache()); QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache());
chatBarCache->setReplyId(u"$153456789:example.org"_s); chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
// These should return empty even though a reply ID has been set because the // These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent. // ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."); QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationAuthor(), Quotient::RoomMember()); QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."); QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString()); QCOMPARE(chatBarCache->relationMessage(), QString());
@@ -75,13 +75,13 @@ void ChatBarCacheTest::badParent()
{ {
QScopedPointer<QObject> badParent(new QObject()); QScopedPointer<QObject> badParent(new QObject());
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(badParent.get())); QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(badParent.get()));
chatBarCache->setReplyId(u"$153456789:example.org"_s); chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
// These should return empty even though a reply ID has been set because the // These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent. // ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation."); QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationAuthor(), Quotient::RoomMember()); QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation."); QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString()); QCOMPARE(chatBarCache->relationMessage(), QString());
@@ -90,57 +90,52 @@ void ChatBarCacheTest::badParent()
void ChatBarCacheTest::reply() void ChatBarCacheTest::reply()
{ {
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room)); QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(u"some text"_s); chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setAttachmentPath(u"some/path"_s); chatBarCache->setAttachmentPath(QLatin1String("some/path"));
chatBarCache->setReplyId(u"$153456789:example.org"_s); chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->text(), u"some text"_s); QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), true); QCOMPARE(chatBarCache->isReplying(), true);
QCOMPARE(chatBarCache->replyId(), u"$153456789:example.org"_s); QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->isEditing(), false); QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString()); QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s)); QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org")));
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s); QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString()); QCOMPARE(chatBarCache->attachmentPath(), QString());
} }
void ChatBarCacheTest::edit() void ChatBarCacheTest::edit()
{ {
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room)); QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
chatBarCache->setText(u"some text"_s); QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
chatBarCache->setAttachmentPath(u"some/path"_s);
connect(chatBarCache.get(), &ChatBarCache::relationIdChanged, this, [](const QString &oldEventId, const QString &newEventId) {
QCOMPARE(oldEventId, QString());
QCOMPARE(newEventId, QString(u"$153456789:example.org"_s));
});
chatBarCache->setEditId(u"$153456789:example.org"_s);
QCOMPARE(chatBarCache->text(), u"some text"_s);
QCOMPARE(chatBarCache->isReplying(), false); QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString()); QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), true); QCOMPARE(chatBarCache->isEditing(), true);
QCOMPARE(chatBarCache->editId(), u"$153456789:example.org"_s); QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s)); QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org")));
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s); QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString()); QCOMPARE(chatBarCache->attachmentPath(), QString());
} }
void ChatBarCacheTest::attachment() void ChatBarCacheTest::attachment()
{ {
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room)); QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(u"some text"_s); chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setEditId(u"$153456789:example.org"_s); chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
chatBarCache->setAttachmentPath(u"some/path"_s); chatBarCache->setAttachmentPath(QLatin1String("some/path"));
QCOMPARE(chatBarCache->text(), u"some text"_s); QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), false); QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString()); QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false); QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString()); QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationAuthor(), room->member(QString())); QCOMPARE(chatBarCache->relationUser(), room->member(QString()));
QCOMPARE(chatBarCache->relationMessage(), QString()); QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), u"some/path"_s); QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
} }
QTEST_MAIN(ChatBarCacheTest) QTEST_MAIN(ChatBarCacheTest)

View File

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

View File

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

View File

@@ -49,51 +49,6 @@
"unsigned": { "unsigned": {
"age": 1234 "age": 1234
} }
},
{
"content": {
"displayname": "Bob",
"membership": "join"
},
"event_id": "$143273blorb3PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:kde.org",
"state_key": "@bob:kde.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"displayname": "Invited",
"membership": "invite"
},
"event_id": "$asdfpj443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@invited:example.com",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"displayname": "Banned",
"membership": "ban"
},
"event_id": "$asdfpj443PhrSnasfd:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@banned:example.com",
"state_key": "@banned:example.com",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
} }
] ]
}, },
@@ -102,7 +57,7 @@
"@alice:example.com", "@alice:example.com",
"@bob:example.com" "@bob:example.com"
], ],
"m.invited_member_count": 1, "m.invited_member_count": 0,
"m.joined_member_count": 2 "m.joined_member_count": 2
}, },
"timeline": { "timeline": {

View File

@@ -8,14 +8,14 @@
"answers": [ "answers": [
{ {
"id": "option1", "id": "option1",
"org.matrix.msc1767.text": "option1text" "org.matrix.msc1767.text": "option1"
}, },
{ {
"id": "option2", "id": "option2",
"org.matrix.msc1767.text": "option2text" "org.matrix.msc1767.text": "option2"
} }
], ],
"kind": "org.matrix.msc3381.poll.undisclosed", "kind": "org.matrix.msc3381.poll.disclosed",
"max_selections": 1, "max_selections": 1,
"question": { "question": {
"body": "test", "body": "test",

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject> #include <QObject>
#include <QQuickItem>
#include <QTest> #include <QTest>
#include "delegatesizehelper.h" #include "delegatesizehelper.h"
@@ -31,7 +30,7 @@ void DelegateSizeHelperTest::risingPercentage_data()
QTest::addColumn<int>("currentPercentageWidth"); QTest::addColumn<int>("currentPercentageWidth");
QTest::addColumn<qreal>("currentWidth"); QTest::addColumn<qreal>("currentWidth");
QTest::newRow("zero") << qreal(0) << int(100) << qreal(0); QTest::newRow("zero") << qreal(0) << int(0) << qreal(0);
QTest::newRow("one hundred") << qreal(100) << int(0) << qreal(0); QTest::newRow("one hundred") << qreal(100) << int(0) << qreal(0);
QTest::newRow("one fifty") << qreal(150) << int(50) << qreal(75); QTest::newRow("one fifty") << qreal(150) << int(50) << qreal(75);
QTest::newRow("two hundred") << qreal(200) << int(100) << qreal(200); QTest::newRow("two hundred") << qreal(200) << int(100) << qreal(200);
@@ -44,18 +43,16 @@ void DelegateSizeHelperTest::risingPercentage()
QFETCH(int, currentPercentageWidth); QFETCH(int, currentPercentageWidth);
QFETCH(qreal, currentWidth); QFETCH(qreal, currentWidth);
auto item = QQuickItem();
item.setWidth(parentWidth);
DelegateSizeHelper delegateSizeHelper; DelegateSizeHelper delegateSizeHelper;
delegateSizeHelper.setParentItem(&item);
delegateSizeHelper.setStartBreakpoint(100); delegateSizeHelper.setStartBreakpoint(100);
delegateSizeHelper.setEndBreakpoint(200); delegateSizeHelper.setEndBreakpoint(200);
delegateSizeHelper.setStartPercentWidth(0); delegateSizeHelper.setStartPercentWidth(0);
delegateSizeHelper.setEndPercentWidth(100); delegateSizeHelper.setEndPercentWidth(100);
QCOMPARE(delegateSizeHelper.availablePercentageWidth(), currentPercentageWidth); delegateSizeHelper.setParentWidth(parentWidth);
QCOMPARE(delegateSizeHelper.availableWidth(), currentWidth);
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
} }
void DelegateSizeHelperTest::fallingPercentage_data() void DelegateSizeHelperTest::fallingPercentage_data()
@@ -77,18 +74,16 @@ void DelegateSizeHelperTest::fallingPercentage()
QFETCH(int, currentPercentageWidth); QFETCH(int, currentPercentageWidth);
QFETCH(qreal, currentWidth); QFETCH(qreal, currentWidth);
auto item = QQuickItem();
item.setWidth(parentWidth);
DelegateSizeHelper delegateSizeHelper; DelegateSizeHelper delegateSizeHelper;
delegateSizeHelper.setParentItem(&item);
delegateSizeHelper.setStartBreakpoint(100); delegateSizeHelper.setStartBreakpoint(100);
delegateSizeHelper.setEndBreakpoint(200); delegateSizeHelper.setEndBreakpoint(200);
delegateSizeHelper.setStartPercentWidth(100); delegateSizeHelper.setStartPercentWidth(100);
delegateSizeHelper.setEndPercentWidth(0); delegateSizeHelper.setEndPercentWidth(0);
QCOMPARE(delegateSizeHelper.availablePercentageWidth(), currentPercentageWidth); delegateSizeHelper.setParentWidth(parentWidth);
QCOMPARE(delegateSizeHelper.availableWidth(), currentWidth);
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
} }
void DelegateSizeHelperTest::equalPercentage_data() void DelegateSizeHelperTest::equalPercentage_data()
@@ -110,18 +105,16 @@ void DelegateSizeHelperTest::equalPercentage()
QFETCH(int, currentPercentageWidth); QFETCH(int, currentPercentageWidth);
QFETCH(qreal, currentWidth); QFETCH(qreal, currentWidth);
auto item = QQuickItem();
item.setWidth(parentWidth);
DelegateSizeHelper delegateSizeHelper; DelegateSizeHelper delegateSizeHelper;
delegateSizeHelper.setParentItem(&item);
delegateSizeHelper.setStartBreakpoint(100); delegateSizeHelper.setStartBreakpoint(100);
delegateSizeHelper.setEndBreakpoint(200); delegateSizeHelper.setEndBreakpoint(200);
delegateSizeHelper.setStartPercentWidth(50); delegateSizeHelper.setStartPercentWidth(50);
delegateSizeHelper.setEndPercentWidth(50); delegateSizeHelper.setEndPercentWidth(50);
QCOMPARE(delegateSizeHelper.availablePercentageWidth(), currentPercentageWidth); delegateSizeHelper.setParentWidth(parentWidth);
QCOMPARE(delegateSizeHelper.availableWidth(), currentWidth);
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
} }
void DelegateSizeHelperTest::equalBreakpoint_data() void DelegateSizeHelperTest::equalBreakpoint_data()
@@ -131,9 +124,9 @@ void DelegateSizeHelperTest::equalBreakpoint_data()
QTest::addColumn<int>("currentPercentageWidth"); QTest::addColumn<int>("currentPercentageWidth");
QTest::addColumn<qreal>("currentWidth"); QTest::addColumn<qreal>("currentWidth");
QTest::newRow("start higher") << int(100) << int(0) << int(100) << qreal(1000); QTest::newRow("start higher") << int(100) << int(0) << int(-1) << qreal(0);
QTest::newRow("equal") << int(50) << int(50) << int(50) << qreal(500); QTest::newRow("equal") << int(50) << int(50) << int(50) << qreal(500);
QTest::newRow("end higher") << int(0) << int(100) << int(100) << qreal(1000); QTest::newRow("end higher") << int(0) << int(100) << int(-1) << qreal(0);
} }
/** /**
@@ -147,18 +140,16 @@ void DelegateSizeHelperTest::equalBreakpoint()
QFETCH(int, currentPercentageWidth); QFETCH(int, currentPercentageWidth);
QFETCH(qreal, currentWidth); QFETCH(qreal, currentWidth);
auto item = QQuickItem();
item.setWidth(1000);
DelegateSizeHelper delegateSizeHelper; DelegateSizeHelper delegateSizeHelper;
delegateSizeHelper.setParentItem(&item);
delegateSizeHelper.setStartBreakpoint(100); delegateSizeHelper.setStartBreakpoint(100);
delegateSizeHelper.setEndBreakpoint(100); delegateSizeHelper.setEndBreakpoint(100);
delegateSizeHelper.setStartPercentWidth(startPercentageWidth); delegateSizeHelper.setStartPercentWidth(startPercentageWidth);
delegateSizeHelper.setEndPercentWidth(endPercentageWidth); delegateSizeHelper.setEndPercentWidth(endPercentageWidth);
QCOMPARE(delegateSizeHelper.availablePercentageWidth(), currentPercentageWidth); delegateSizeHelper.setParentWidth(1000);
QCOMPARE(delegateSizeHelper.availableWidth(), currentWidth);
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
} }
QTEST_GUILESS_MAIN(DelegateSizeHelperTest) QTEST_GUILESS_MAIN(DelegateSizeHelperTest)

View File

@@ -11,8 +11,6 @@
#include <Quotient/connection.h> #include <Quotient/connection.h>
#include <Quotient/quotient_common.h> #include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h> #include <Quotient/syncdata.h>
#include <qcbormap.h>
#include <qtestcase.h>
#include "linkpreviewer.h" #include "linkpreviewer.h"
#include "models/reactionmodel.h" #include "models/reactionmodel.h"
@@ -31,9 +29,13 @@ private:
Connection *connection = nullptr; Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr; TestUtils::TestRoom *room = nullptr;
EventHandler emptyHandler = EventHandler(nullptr, nullptr);
private Q_SLOTS: private Q_SLOTS:
void initTestCase(); void initTestCase();
void eventId();
void nullEventId();
void authorDisplayName(); void authorDisplayName();
void nullAuthorDisplayName(); void nullAuthorDisplayName();
void singleLineSidplayName(); void singleLineSidplayName();
@@ -41,6 +43,7 @@ private Q_SLOTS:
void time(); void time();
void nullTime(); void nullTime();
void timeString(); void timeString();
void nullTimeString();
void highlighted(); void highlighted();
void nullHighlighted(); void nullHighlighted();
void hidden(); void hidden();
@@ -56,153 +59,181 @@ private Q_SLOTS:
void nullSubtitle(); void nullSubtitle();
void mediaInfo(); void mediaInfo();
void nullMediaInfo(); void nullMediaInfo();
void hasReply();
void nullHasReply();
void replyId();
void nullReplyId();
void replyAuthor(); void replyAuthor();
void nullReplyAuthor(); void nullReplyAuthor();
void replyBody();
void nullReplyBody();
void replyMediaInfo();
void nullReplyMediaInfo();
void thread();
void nullThread();
void location(); void location();
void nullLocation(); void nullLocation();
}; };
void EventHandlerTest::initTestCase() void EventHandlerTest::initTestCase()
{ {
connection = Connection::makeMockConnection(u"@bob:kde.org"_s); connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-eventhandler-sync.json"_s); room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-eventhandler-sync.json"));
}
void EventHandlerTest::eventId()
{
EventHandler eventHandler(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandler.getId(), QStringLiteral("$153456789:example.org"));
}
void EventHandlerTest::nullEventId()
{
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getId called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getId(), QString());
} }
void EventHandlerTest::authorDisplayName() void EventHandlerTest::authorDisplayName()
{ {
QCOMPARE(EventHandler::authorDisplayName(room, room->messageEvents().at(1).get()), u"before"_s); EventHandler eventHandler(room, room->messageEvents().at(1).get());
QCOMPARE(eventHandler.getAuthorDisplayName(), QStringLiteral("before"));
} }
void EventHandlerTest::nullAuthorDisplayName() void EventHandlerTest::nullAuthorDisplayName()
{ {
QTest::ignoreMessage(QtWarningMsg, "authorDisplayName called with room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
QCOMPARE(EventHandler::authorDisplayName(nullptr, nullptr), QString()); QCOMPARE(emptyHandler.getAuthorDisplayName(), QString());
QTest::ignoreMessage(QtWarningMsg, "authorDisplayName called with event set to nullptr."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::authorDisplayName(room, nullptr), QString()); QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getAuthorDisplayName(), QString());
} }
void EventHandlerTest::singleLineSidplayName() void EventHandlerTest::singleLineSidplayName()
{ {
QCOMPARE(EventHandler::singleLineAuthorDisplayname(room, room->messageEvents().at(11).get()), "Look at me I put newlines in my display name"_L1); EventHandler eventHandler(room, room->messageEvents().at(11).get());
QCOMPARE(eventHandler.singleLineAuthorDisplayname(), QStringLiteral("Look at me I put newlines in my display name"));
} }
void EventHandlerTest::nullSingleLineDisplayName() void EventHandlerTest::nullSingleLineDisplayName()
{ {
QTest::ignoreMessage(QtWarningMsg, "singleLineAuthorDisplayname called with room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
QCOMPARE(EventHandler::singleLineAuthorDisplayname(nullptr, nullptr), QString()); QCOMPARE(emptyHandler.singleLineAuthorDisplayname(), QString());
QTest::ignoreMessage(QtWarningMsg, "singleLineAuthorDisplayname called with event set to nullptr."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::singleLineAuthorDisplayname(room, nullptr), QString()); QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
QCOMPARE(noEventHandler.singleLineAuthorDisplayname(), QString());
} }
void EventHandlerTest::time() void EventHandlerTest::time()
{ {
const auto event = room->messageEvents().at(0).get(); EventHandler eventHandler(room, room->messageEvents().at(0).get());
QCOMPARE(EventHandler::time(room, event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC))); QCOMPARE(eventHandler.getTime(), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
QCOMPARE(eventHandler.getTime(true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
QCOMPARE(room->pendingEvents().size(), 1);
const auto pendingIt = room->findPendingEvent(txID);
QCOMPARE(EventHandler::time(room, pendingIt->event(), true), pendingIt->lastUpdated());
room->discardMessage(txID);
QCOMPARE(room->pendingEvents().size(), 0);
} }
void EventHandlerTest::nullTime() void EventHandlerTest::nullTime()
{ {
QTest::ignoreMessage(QtWarningMsg, "time called with room set to nullptr."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::time(nullptr, nullptr), QDateTime()); QTest::ignoreMessage(QtWarningMsg, "getTime called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getTime(), QDateTime());
QTest::ignoreMessage(QtWarningMsg, "time called with event set to nullptr."); EventHandler eventHandler(room, room->messageEvents().at(0).get());
QCOMPARE(EventHandler::time(room, nullptr), QDateTime()); QTest::ignoreMessage(QtWarningMsg, "a value must be provided for lastUpdated for a pending event.");
QCOMPARE(eventHandler.getTime(true), QDateTime());
} }
void EventHandlerTest::timeString() void EventHandlerTest::timeString()
{ {
const auto event = room->messageEvents().at(0).get(); EventHandler eventHandler(room, room->messageEvents().at(0).get());
KFormat format; KFormat format;
QCOMPARE(EventHandler::timeString(room, event, false), QCOMPARE(eventHandler.getTimeString(false),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat)); QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, true), QCOMPARE(eventHandler.getTimeString(true),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat)); format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s), QCOMPARE(eventHandler.getTimeString(false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::LocalTime)).toString(u"hh:mm"_s)); QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(eventHandler.getTimeString(true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(eventHandler.getTimeString(false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
QCOMPARE(eventHandler.getTimeString(true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
QCOMPARE(eventHandler.getTimeString(QStringLiteral("hh:mm")), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toString(QStringLiteral("hh:mm")));
}
const auto txID = room->postJson("m.room.message"_L1, event->fullJson()); void EventHandlerTest::nullTimeString()
QCOMPARE(room->pendingEvents().size(), 1); {
const auto pendingIt = room->findPendingEvent(txID); EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getTimeString called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getTimeString(false), QString());
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::ShortFormat, true), EventHandler eventHandler(room, room->messageEvents().at(0).get());
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::ShortFormat)); QTest::ignoreMessage(QtWarningMsg, "a value must be provided for lastUpdated for a pending event.");
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::ShortFormat, true), QCOMPARE(eventHandler.getTimeString(false, QLocale::ShortFormat, true), QString());
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::LongFormat, true),
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::LongFormat));
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::LongFormat, true),
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::LongFormat));
room->discardMessage(txID);
QCOMPARE(room->pendingEvents().size(), 0);
} }
void EventHandlerTest::highlighted() void EventHandlerTest::highlighted()
{ {
QCOMPARE(EventHandler::isHighlighted(room, room->messageEvents().at(2).get()), true); EventHandler eventHandlerHighlight(room, room->messageEvents().at(2).get());
QCOMPARE(EventHandler::isHighlighted(room, room->messageEvents().at(0).get()), false); QCOMPARE(eventHandlerHighlight.isHighlighted(), true);
EventHandler eventHandlerNoHighlight(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandlerNoHighlight.isHighlighted(), false);
} }
void EventHandlerTest::nullHighlighted() void EventHandlerTest::nullHighlighted()
{ {
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_room set to nullptr.");
QCOMPARE(EventHandler::isHighlighted(nullptr, nullptr), false); QCOMPARE(emptyHandler.isHighlighted(), false);
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with event set to nullptr."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::isHighlighted(room, nullptr), false); QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_event set to nullptr.");
QCOMPARE(noEventHandler.isHighlighted(), false);
} }
void EventHandlerTest::hidden() void EventHandlerTest::hidden()
{ {
QCOMPARE(EventHandler::isHidden(room, room->messageEvents().at(3).get()), true); EventHandler eventHandlerHidden(room, room->messageEvents().at(3).get());
QCOMPARE(EventHandler::isHidden(room, room->messageEvents().at(0).get()), false); QCOMPARE(eventHandlerHidden.isHidden(), true);
EventHandler eventHandlerNoHidden(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandlerNoHidden.isHidden(), false);
} }
void EventHandlerTest::nullHidden() void EventHandlerTest::nullHidden()
{ {
QTest::ignoreMessage(QtWarningMsg, "isHidden called with room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_room set to nullptr.");
QCOMPARE(EventHandler::isHidden(nullptr, nullptr), false); QCOMPARE(emptyHandler.isHidden(), false);
QTest::ignoreMessage(QtWarningMsg, "isHidden called with event set to nullptr."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::isHidden(room, nullptr), false); QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_event set to nullptr.");
QCOMPARE(noEventHandler.isHidden(), false);
} }
void EventHandlerTest::body() void EventHandlerTest::body()
{ {
const auto event = room->messageEvents().at(0).get(); EventHandler eventHandler(room, room->messageEvents().at(0).get());
QCOMPARE(EventHandler::richBody(room, event), u"<b>This is an example<br>text message</b>"_s); QCOMPARE(eventHandler.getRichBody(), QStringLiteral("<b>This is an example<br>text message</b>"));
QCOMPARE(EventHandler::richBody(room, event, true), u"<b>This is an example text message</b>"_s); QCOMPARE(eventHandler.getRichBody(true), QStringLiteral("<b>This is an example text message</b>"));
QCOMPARE(EventHandler::plainBody(room, event), u"This is an example\ntext message"_s); QCOMPARE(eventHandler.getPlainBody(), QStringLiteral("This is an example\ntext message"));
QCOMPARE(EventHandler::plainBody(room, event, true), u"This is an example text message"_s); QCOMPARE(eventHandler.getPlainBody(true), QStringLiteral("This is an example text message"));
} }
void EventHandlerTest::nullBody() void EventHandlerTest::nullBody()
{ {
QTest::ignoreMessage(QtWarningMsg, "richBody called with room set to nullptr."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::richBody(nullptr, nullptr), QString());
QTest::ignoreMessage(QtWarningMsg, "richBody called with event set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getRichBody called with m_event set to nullptr.");
QCOMPARE(EventHandler::richBody(room, nullptr), QString()); QCOMPARE(noEventHandler.getRichBody(), QString());
QTest::ignoreMessage(QtWarningMsg, "plainBody called with room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getPlainBody called with m_event set to nullptr.");
QCOMPARE(EventHandler::plainBody(nullptr, nullptr), QString()); QCOMPARE(noEventHandler.getPlainBody(), QString());
QTest::ignoreMessage(QtWarningMsg, "plainBody called with event set to nullptr.");
QCOMPARE(EventHandler::plainBody(room, nullptr), QString());
} }
void EventHandlerTest::genericBody_data() void EventHandlerTest::genericBody_data()
@@ -210,11 +241,11 @@ void EventHandlerTest::genericBody_data()
QTest::addColumn<int>("eventNum"); QTest::addColumn<int>("eventNum");
QTest::addColumn<QString>("output"); QTest::addColumn<QString>("output");
QTest::newRow("message") << 0 << u"<a href=\"https://matrix.to/#/@example:example.org\">after</a> sent a message"_s; QTest::newRow("message") << 0 << QStringLiteral("sent a message");
QTest::newRow("member") << 1 << u"<a href=\"https://matrix.to/#/@example:example.org\">after</a> changed their display name and updated their avatar"_s; QTest::newRow("member") << 1 << QStringLiteral("changed their display name and updated their avatar");
QTest::newRow("message 2") << 2 << u"<a href=\"https://matrix.to/#/@example:example.org\">after</a> sent a message"_s; QTest::newRow("message 2") << 2 << QStringLiteral("sent a message");
QTest::newRow("reaction") << 3 << u"Unknown event"_s; QTest::newRow("reaction") << 3 << QStringLiteral("Unknown event");
QTest::newRow("video") << 4 << u"<a href=\"https://matrix.to/#/@example:example.org\">after</a> sent a message"_s; QTest::newRow("video") << 4 << QStringLiteral("sent a message");
} }
void EventHandlerTest::genericBody() void EventHandlerTest::genericBody()
@@ -222,79 +253,120 @@ void EventHandlerTest::genericBody()
QFETCH(int, eventNum); QFETCH(int, eventNum);
QFETCH(QString, output); QFETCH(QString, output);
QCOMPARE(EventHandler::genericBody(room, room->messageEvents().at(eventNum).get()), output); EventHandler eventHandler(room, room->messageEvents().at(eventNum).get());
QCOMPARE(eventHandler.getGenericBody(), output);
} }
void EventHandlerTest::nullGenericBody() void EventHandlerTest::nullGenericBody()
{ {
QTest::ignoreMessage(QtWarningMsg, "genericBody called with room set to nullptr."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::genericBody(nullptr, nullptr), QString()); QTest::ignoreMessage(QtWarningMsg, "getGenericBody called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getGenericBody(), QString());
QTest::ignoreMessage(QtWarningMsg, "genericBody called with event set to nullptr.");
QCOMPARE(EventHandler::genericBody(room, nullptr), QString());
} }
void EventHandlerTest::markdownBody() void EventHandlerTest::markdownBody()
{ {
QCOMPARE(EventHandler::markdownBody(room->messageEvents().at(0).get()), u"This is an example\ntext message"_s); EventHandler eventHandler(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
} }
void EventHandlerTest::markdownBodyReply() void EventHandlerTest::markdownBodyReply()
{ {
QCOMPARE(EventHandler::markdownBody(room->messageEvents().at(5).get()), u"reply"_s); EventHandler eventHandler(room, room->messageEvents().at(5).get());
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("reply"));
} }
void EventHandlerTest::subtitle() void EventHandlerTest::subtitle()
{ {
QCOMPARE(EventHandler::subtitleText(room, room->messageEvents().at(0).get()), u"after: This is an example text message"_s); EventHandler eventHandler(room, room->messageEvents().at(0).get());
QCOMPARE(EventHandler::subtitleText(room, room->messageEvents().at(2).get()), QCOMPARE(eventHandler.subtitleText(), QStringLiteral("after: This is an example text message"));
u"after: This is a highlight @bob:kde.org and this is a link https://kde.org"_s);
EventHandler eventHandler2(room, room->messageEvents().at(2).get());
QCOMPARE(eventHandler2.subtitleText(), QStringLiteral("after: This is a highlight @bob:kde.org and this is a link https://kde.org"));
} }
void EventHandlerTest::nullSubtitle() void EventHandlerTest::nullSubtitle()
{ {
QTest::ignoreMessage(QtWarningMsg, "subtitleText called with room set to nullptr."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::subtitleText(nullptr, nullptr), QString()); QTest::ignoreMessage(QtWarningMsg, "subtitleText called with m_event set to nullptr.");
QCOMPARE(noEventHandler.subtitleText(), QString());
QTest::ignoreMessage(QtWarningMsg, "subtitleText called with event set to nullptr.");
QCOMPARE(EventHandler::subtitleText(room, nullptr), QString());
} }
void EventHandlerTest::mediaInfo() void EventHandlerTest::mediaInfo()
{ {
auto event = room->messageEvents().at(4).get(); auto event = room->messageEvents().at(4).get();
auto mediaInfo = EventHandler::mediaInfo(room, event); EventHandler eventHandler(room, event);
auto thumbnailInfo = mediaInfo["tempInfo"_L1].toMap();
QCOMPARE(mediaInfo["source"_L1], room->makeMediaUrl(event->id(), QUrl("mxc://kde.org/1234567"_L1))); auto mediaInfo = eventHandler.getMediaInfo();
QCOMPARE(mediaInfo["mimeType"_L1], u"video/mp4"_s); auto thumbnailInfo = mediaInfo["tempInfo"_ls].toMap();
QCOMPARE(mediaInfo["mimeIcon"_L1], u"video-mp4"_s);
QCOMPARE(mediaInfo["size"_L1], 62650636); QCOMPARE(mediaInfo["source"_ls], room->makeMediaUrl(event->id(), QUrl("mxc://kde.org/1234567"_ls)));
QCOMPARE(mediaInfo["duration"_L1], 10); QCOMPARE(mediaInfo["mimeType"_ls], QStringLiteral("video/mp4"));
QCOMPARE(mediaInfo["width"_L1], 1920); QCOMPARE(mediaInfo["mimeIcon"_ls], QStringLiteral("video-mp4"));
QCOMPARE(mediaInfo["height"_L1], 1080); QCOMPARE(mediaInfo["size"_ls], 62650636);
QCOMPARE(thumbnailInfo["source"_L1], room->makeMediaUrl(event->id(), QUrl("mxc://kde.org/2234567"_L1))); QCOMPARE(mediaInfo["duration"_ls], 10);
QCOMPARE(thumbnailInfo["mimeType"_L1], u"image/jpeg"_s); QCOMPARE(mediaInfo["width"_ls], 1920);
QCOMPARE(thumbnailInfo["mimeIcon"_L1], u"image-jpeg"_s); QCOMPARE(mediaInfo["height"_ls], 1080);
QCOMPARE(thumbnailInfo["size"_L1], 382249); QCOMPARE(thumbnailInfo["source"_ls], room->makeMediaUrl(event->id(), QUrl("mxc://kde.org/2234567"_ls)));
QCOMPARE(thumbnailInfo["width"_L1], 800); QCOMPARE(thumbnailInfo["mimeType"_ls], QStringLiteral("image/jpeg"));
QCOMPARE(thumbnailInfo["height"_L1], 450); QCOMPARE(thumbnailInfo["mimeIcon"_ls], QStringLiteral("image-jpeg"));
QCOMPARE(thumbnailInfo["size"_ls], 382249);
QCOMPARE(thumbnailInfo["width"_ls], 800);
QCOMPARE(thumbnailInfo["height"_ls], 450);
} }
void EventHandlerTest::nullMediaInfo() void EventHandlerTest::nullMediaInfo()
{ {
QTest::ignoreMessage(QtWarningMsg, "mediaInfo called with room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_room set to nullptr.");
QCOMPARE(EventHandler::mediaInfo(nullptr, nullptr), QVariantMap()); QCOMPARE(emptyHandler.getMediaInfo(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "mediaInfo called with event set to nullptr."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::mediaInfo(room, nullptr), QVariantMap()); QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
}
void EventHandlerTest::hasReply()
{
EventHandler eventHandlerReply(room, room->messageEvents().at(5).get());
QCOMPARE(eventHandlerReply.hasReply(), true);
EventHandler eventHandlerNoReply(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandlerNoReply.hasReply(), false);
}
void EventHandlerTest::nullHasReply()
{
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "hasReply called with m_event set to nullptr.");
QCOMPARE(noEventHandler.hasReply(), false);
}
void EventHandlerTest::replyId()
{
EventHandler eventHandlerReply(room, room->messageEvents().at(5).get());
QCOMPARE(eventHandlerReply.getReplyId(), QStringLiteral("$153456789:example.org"));
EventHandler eventHandlerNoReply(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandlerNoReply.getReplyId(), QStringLiteral(""));
}
void EventHandlerTest::nullReplyId()
{
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getReplyId called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReplyId(), QString());
} }
void EventHandlerTest::replyAuthor() void EventHandlerTest::replyAuthor()
{ {
auto replyEvent = room->messageEvents().at(0).get(); auto replyEvent = room->messageEvents().at(0).get();
auto replyAuthor = room->member(replyEvent->senderId()); auto replyAuthor = room->member(replyEvent->senderId());
auto eventHandlerReplyAuthor = EventHandler::replyAuthor(room, room->messageEvents().at(5).get()); EventHandler eventHandler(room, room->messageEvents().at(5).get());
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
QCOMPARE(eventHandlerReplyAuthor.isLocalMember(), replyAuthor.id() == room->localMember().id()); QCOMPARE(eventHandlerReplyAuthor.isLocalMember(), replyAuthor.id() == room->localMember().id());
QCOMPARE(eventHandlerReplyAuthor.id(), replyAuthor.id()); QCOMPARE(eventHandlerReplyAuthor.id(), replyAuthor.id());
@@ -303,35 +375,121 @@ void EventHandlerTest::replyAuthor()
QCOMPARE(eventHandlerReplyAuthor.avatarMediaId(), replyAuthor.avatarMediaId()); QCOMPARE(eventHandlerReplyAuthor.avatarMediaId(), replyAuthor.avatarMediaId());
QCOMPARE(eventHandlerReplyAuthor.color(), replyAuthor.color()); QCOMPARE(eventHandlerReplyAuthor.color(), replyAuthor.color());
QCOMPARE(EventHandler::replyAuthor(room, room->messageEvents().at(0).get()), RoomMember()); EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), RoomMember());
} }
void EventHandlerTest::nullReplyAuthor() void EventHandlerTest::nullReplyAuthor()
{ {
QTest::ignoreMessage(QtWarningMsg, "replyAuthor called with room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
QCOMPARE(EventHandler::replyAuthor(nullptr, nullptr), RoomMember()); QCOMPARE(emptyHandler.getReplyAuthor(), RoomMember());
QTest::ignoreMessage(QtWarningMsg, "replyAuthor called with event set to nullptr. Returning empty user."); EventHandler noEventHandler(room, nullptr);
QCOMPARE(EventHandler::replyAuthor(room, nullptr), RoomMember()); QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user.");
QCOMPARE(noEventHandler.getReplyAuthor(), RoomMember());
}
void EventHandlerTest::replyBody()
{
EventHandler eventHandler(room, room->messageEvents().at(5).get());
QCOMPARE(eventHandler.getReplyRichBody(), QStringLiteral("<b>This is an example<br>text message</b>"));
QCOMPARE(eventHandler.getReplyRichBody(true), QStringLiteral("<b>This is an example text message</b>"));
QCOMPARE(eventHandler.getReplyPlainBody(), QStringLiteral("This is an example\ntext message"));
QCOMPARE(eventHandler.getReplyPlainBody(true), QStringLiteral("This is an example text message"));
}
void EventHandlerTest::nullReplyBody()
{
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getReplyRichBody called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReplyRichBody(), QString());
QTest::ignoreMessage(QtWarningMsg, "getReplyPlainBody called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReplyPlainBody(), QString());
}
void EventHandlerTest::replyMediaInfo()
{
auto event = room->messageEvents().at(6).get();
auto replyEvent = room->messageEvents().at(4).get();
EventHandler eventHandler(room, event);
auto mediaInfo = eventHandler.getReplyMediaInfo();
auto thumbnailInfo = mediaInfo["tempInfo"_ls].toMap();
QCOMPARE(mediaInfo["source"_ls], room->makeMediaUrl(replyEvent->id(), QUrl("mxc://kde.org/1234567"_ls)));
QCOMPARE(mediaInfo["mimeType"_ls], QStringLiteral("video/mp4"));
QCOMPARE(mediaInfo["mimeIcon"_ls], QStringLiteral("video-mp4"));
QCOMPARE(mediaInfo["size"_ls], 62650636);
QCOMPARE(mediaInfo["duration"_ls], 10);
QCOMPARE(mediaInfo["width"_ls], 1920);
QCOMPARE(mediaInfo["height"_ls], 1080);
QCOMPARE(thumbnailInfo["source"_ls], room->makeMediaUrl(replyEvent->id(), QUrl("mxc://kde.org/2234567"_ls)));
QCOMPARE(thumbnailInfo["mimeType"_ls], QStringLiteral("image/jpeg"));
QCOMPARE(thumbnailInfo["mimeIcon"_ls], QStringLiteral("image-jpeg"));
QCOMPARE(thumbnailInfo["size"_ls], 382249);
QCOMPARE(thumbnailInfo["width"_ls], 800);
QCOMPARE(thumbnailInfo["height"_ls], 450);
}
void EventHandlerTest::nullReplyMediaInfo()
{
QTest::ignoreMessage(QtWarningMsg, "getReplyMediaInfo called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReplyMediaInfo(), QVariantMap());
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getReplyMediaInfo called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReplyMediaInfo(), QVariantMap());
}
void EventHandlerTest::thread()
{
EventHandler eventHandlerNoThread(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandlerNoThread.isThreaded(), false);
QCOMPARE(eventHandlerNoThread.threadRoot(), QString());
EventHandler eventHandlerThreadRoot(room, room->messageEvents().at(9).get());
QCOMPARE(eventHandlerThreadRoot.isThreaded(), true);
QCOMPARE(eventHandlerThreadRoot.threadRoot(), QStringLiteral("$threadroot:example.org"));
QCOMPARE(eventHandlerThreadRoot.getReplyId(), QStringLiteral("$threadroot:example.org"));
EventHandler eventHandlerThreadReply(room, room->messageEvents().at(10).get());
QCOMPARE(eventHandlerThreadReply.isThreaded(), true);
QCOMPARE(eventHandlerThreadReply.threadRoot(), QStringLiteral("$threadroot:example.org"));
QCOMPARE(eventHandlerThreadReply.getReplyId(), QStringLiteral("$threadmessage1:example.org"));
}
void EventHandlerTest::nullThread()
{
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with m_event set to nullptr.");
QCOMPARE(emptyHandler.isThreaded(), false);
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with m_event set to nullptr.");
QCOMPARE(noEventHandler.threadRoot(), QString());
} }
void EventHandlerTest::location() void EventHandlerTest::location()
{ {
QCOMPARE(EventHandler::latitude(room->messageEvents().at(7).get()), u"51.7035"_s.toFloat()); EventHandler eventHandler(room, room->messageEvents().at(7).get());
QCOMPARE(EventHandler::longitude(room->messageEvents().at(7).get()), u"-1.14394"_s.toFloat());
QCOMPARE(EventHandler::locationAssetType(room->messageEvents().at(7).get()), u"m.pin"_s); QCOMPARE(eventHandler.getLatitude(), QStringLiteral("51.7035").toFloat());
QCOMPARE(eventHandler.getLongitude(), QStringLiteral("-1.14394").toFloat());
QCOMPARE(eventHandler.getLocationAssetType(), QStringLiteral("m.pin"));
} }
void EventHandlerTest::nullLocation() void EventHandlerTest::nullLocation()
{ {
QTest::ignoreMessage(QtWarningMsg, "latitude called with event set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getLatitude called with m_event set to nullptr.");
QCOMPARE(EventHandler::latitude(nullptr), -100.0); QCOMPARE(emptyHandler.getLatitude(), -100.0);
QTest::ignoreMessage(QtWarningMsg, "longitude called with event set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getLongitude called with m_event set to nullptr.");
QCOMPARE(EventHandler::longitude(nullptr), -200.0); QCOMPARE(emptyHandler.getLongitude(), -200.0);
QTest::ignoreMessage(QtWarningMsg, "locationAssetType called with event set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getLocationAssetType called with m_event set to nullptr.");
QCOMPARE(EventHandler::locationAssetType(nullptr), QString()); QCOMPARE(emptyHandler.getLocationAssetType(), QString());
} }
QTEST_MAIN(EventHandlerTest) QTEST_MAIN(EventHandlerTest)

View File

@@ -38,8 +38,8 @@ private Q_SLOTS:
void LinkPreviewerTest::initTestCase() void LinkPreviewerTest::initTestCase()
{ {
connection = Connection::makeMockConnection(u"@bob:example.org"_s); connection = Connection::makeMockConnection(QStringLiteral("@bob:example.org"));
room = new TestUtils::TestRoom(connection, u"!test:example.org"_s); room = new TestUtils::TestRoom(connection, QStringLiteral("!test:example.org"));
} }
void LinkPreviewerTest::linkPreviewsMatch_data() void LinkPreviewerTest::linkPreviewsMatch_data()
@@ -47,9 +47,9 @@ void LinkPreviewerTest::linkPreviewsMatch_data()
QTest::addColumn<QString>("inputString"); QTest::addColumn<QString>("inputString");
QTest::addColumn<QUrl>("testOutputLink"); QTest::addColumn<QUrl>("testOutputLink");
QTest::newRow("plainHttps") << u"https://kde.org"_s << QUrl(u"https://kde.org"_s); QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttps") << u"<a href=\"https://kde.org\">Rich Link</a>"_s << QUrl(u"https://kde.org"_s); QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttpsLinkDescription") << u"<a href=\"https://kde.org\">https://kde.org</a>"_s << QUrl(u"https://kde.org"_s); QTest::newRow("richHttpsLinkDescription") << QStringLiteral("<a href=\"https://kde.org\">https://kde.org</a>") << QUrl("https://kde.org"_ls);
} }
void LinkPreviewerTest::linkPreviewsMatch() void LinkPreviewerTest::linkPreviewsMatch()
@@ -67,8 +67,8 @@ void LinkPreviewerTest::multipleLinkPreviewsMatch_data()
QTest::addColumn<QString>("inputString"); QTest::addColumn<QString>("inputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks"); QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("multipleHttps") << u"www.example.org https://kde.org"_s << QList{QUrl(u"www.example.org"_s), QUrl(u"https://kde.org"_s)}; QTest::newRow("multipleHttps") << QStringLiteral("www.example.org https://kde.org") << QList{QUrl("www.example.org"_ls), QUrl("https://kde.org"_ls)};
QTest::newRow("multipleHttps1Invalid") << u"www.example.org mxc://example.org/SEsfnsuifSDFSSEF"_s << QList{QUrl(u"www.example.org"_s)}; QTest::newRow("multipleHttps1Invalid") << QStringLiteral("www.example.org mxc://example.org/SEsfnsuifSDFSSEF") << QList{QUrl("www.example.org"_ls)};
} }
void LinkPreviewerTest::multipleLinkPreviewsMatch() void LinkPreviewerTest::multipleLinkPreviewsMatch()
@@ -85,9 +85,9 @@ void LinkPreviewerTest::linkPreviewsReject_data()
{ {
QTest::addColumn<QString>("inputString"); QTest::addColumn<QString>("inputString");
QTest::newRow("mxc") << u"mxc://example.org/SEsfnsuifSDFSSEF"_s; QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF");
QTest::newRow("matrixTo") << u"https://matrix.to/#/@alice:example.org"_s; QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org");
QTest::newRow("noSpace") << u"testhttps://kde.org"_s; QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org");
} }
void LinkPreviewerTest::linkPreviewsReject() void LinkPreviewerTest::linkPreviewsReject()

View File

@@ -25,7 +25,6 @@ private Q_SLOTS:
void MediaSizeHelperTest::uninitialized() void MediaSizeHelperTest::uninitialized()
{ {
MediaSizeHelper mediasizehelper; MediaSizeHelper mediasizehelper;
mediasizehelper.setMaxSize(540, 540);
QCOMPARE(mediasizehelper.currentSize(), QSize(540, qRound(qreal(NeoChatConfig::self()->mediaMaxWidth()) / qreal(16.0) * qreal(9.0)))); QCOMPARE(mediasizehelper.currentSize(), QSize(540, qRound(qreal(NeoChatConfig::self()->mediaMaxWidth()) / qreal(16.0) * qreal(9.0))));
} }
@@ -61,7 +60,6 @@ void MediaSizeHelperTest::limits()
QFETCH(QSize, currentSize); QFETCH(QSize, currentSize);
MediaSizeHelper mediasizehelper; MediaSizeHelper mediasizehelper;
mediasizehelper.setMaxSize(540, 540);
mediasizehelper.setMediaWidth(mediaWidth); mediasizehelper.setMediaWidth(mediaWidth);
mediasizehelper.setMediaHeight(mediaHeight); mediasizehelper.setMediaHeight(mediaHeight);
mediasizehelper.setContentMaxWidth(contentMaxWidth); mediasizehelper.setContentMaxWidth(contentMaxWidth);

View File

@@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject>
#include <QSignalSpy>
#include <QTest>
#include <Quotient/connection.h>
#include <Quotient/quotient_common.h>
#include <Quotient/roommember.h>
#include <Quotient/syncdata.h>
#include "models/messagecontentmodel.h"
#include "neochatconnection.h"
#include "testutils.h"
using namespace Quotient;
using namespace Qt::Literals::StringLiterals;
class MessageContentModelTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
private Q_SLOTS:
void initTestCase();
void missingEvent();
};
void MessageContentModelTest::initTestCase()
{
connection = new NeoChatConnection;
}
void MessageContentModelTest::missingEvent()
{
auto room = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
auto model1 = MessageContentModel(room, u"$153456789:example.org"_s);
QCOMPARE(model1.rowCount(), 1);
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading"_s);
auto model2 = MessageContentModel(room, u"$153456789:example.org"_s, true);
QCOMPARE(model2.rowCount(), 1);
QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply"_s);
room->syncNewEvents(u"test-min-sync.json"_s);
QCOMPARE(model1.rowCount(), 2);
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Author);
QCOMPARE(model1.data(model1.index(1), MessageContentModel::ComponentTypeRole), MessageComponentType::Text);
QCOMPARE(model1.data(model1.index(1), MessageContentModel::DisplayRole), u"<b>This is an example<br>text message</b>"_s);
}
QTEST_MAIN(MessageContentModelTest)
#include "messagecontentmodeltest.moc"

View File

@@ -10,20 +10,20 @@
#include <Quotient/syncdata.h> #include <Quotient/syncdata.h>
#include "enums/delegatetype.h" #include "enums/delegatetype.h"
#include "models/timelinemessagemodel.h" #include "models/messageeventmodel.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "testutils.h" #include "testutils.h"
using namespace Quotient; using namespace Quotient;
class TimelineMessageModelTest : public QObject class MessageEventModelTest : public QObject
{ {
Q_OBJECT Q_OBJECT
private: private:
Connection *connection = nullptr; Connection *connection = nullptr;
TimelineMessageModel *model = nullptr; MessageEventModel *model = nullptr;
private Q_SLOTS: private Q_SLOTS:
void initTestCase(); void initTestCase();
@@ -40,70 +40,70 @@ private Q_SLOTS:
void cleanup(); void cleanup();
}; };
void TimelineMessageModelTest::initTestCase() void MessageEventModelTest::initTestCase()
{ {
connection = Connection::makeMockConnection(u"@bob:kde.org"_s); connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
} }
void TimelineMessageModelTest::init() void MessageEventModelTest::init()
{ {
QCOMPARE(model, nullptr); QCOMPARE(model, nullptr);
model = new TimelineMessageModel; model = new MessageEventModel;
} }
// Make sure that basic empty rooms can be switched without crashing. // Make sure that basic empty rooms can be switched without crashing.
void TimelineMessageModelTest::switchEmptyRoom() void MessageEventModelTest::switchEmptyRoom()
{ {
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s); auto firstRoom = new TestUtils::TestRoom(connection, QStringLiteral("#firstRoom:kde.org"));
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s); auto secondRoom = new TestUtils::TestRoom(connection, QStringLiteral("#secondRoom:kde.org"));
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *))); QSignalSpy spy(model, SIGNAL(roomChanged()));
QCOMPARE(model->room(), nullptr); QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom); model->setRoom(firstRoom);
QCOMPARE(spy.count(), 1); QCOMPARE(spy.count(), 1);
QCOMPARE(model->room()->id(), u"#firstRoom:kde.org"_s); QCOMPARE(model->room()->id(), QStringLiteral("#firstRoom:kde.org"));
model->setRoom(secondRoom); model->setRoom(secondRoom);
QCOMPARE(spy.count(), 2); QCOMPARE(spy.count(), 2);
QCOMPARE(model->room()->id(), u"#secondRoom:kde.org"_s); QCOMPARE(model->room()->id(), QStringLiteral("#secondRoom:kde.org"));
model->setRoom(nullptr); model->setRoom(nullptr);
QCOMPARE(spy.count(), 3); QCOMPARE(spy.count(), 3);
QCOMPARE(model->room(), nullptr); QCOMPARE(model->room(), nullptr);
} }
// Make sure that rooms with some events can be switched without crashing // Make sure that rooms with some events can be switched without crashing
void TimelineMessageModelTest::switchSyncedRoom() void MessageEventModelTest::switchSyncedRoom()
{ {
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s); auto firstRoom = new TestUtils::TestRoom(connection, QStringLiteral("#firstRoom:kde.org"), QLatin1String("test-messageventmodel-sync.json"));
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s); auto secondRoom = new TestUtils::TestRoom(connection, QStringLiteral("#secondRoom:kde.org"), QLatin1String("test-messageventmodel-sync.json"));
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *))); QSignalSpy spy(model, SIGNAL(roomChanged()));
QCOMPARE(model->room(), nullptr); QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom); model->setRoom(firstRoom);
QCOMPARE(spy.count(), 1); QCOMPARE(spy.count(), 1);
QCOMPARE(model->room()->id(), u"#firstRoom:kde.org"_s); QCOMPARE(model->room()->id(), QStringLiteral("#firstRoom:kde.org"));
model->setRoom(secondRoom); model->setRoom(secondRoom);
QCOMPARE(spy.count(), 2); QCOMPARE(spy.count(), 2);
QCOMPARE(model->room()->id(), u"#secondRoom:kde.org"_s); QCOMPARE(model->room()->id(), QStringLiteral("#secondRoom:kde.org"));
model->setRoom(nullptr); model->setRoom(nullptr);
QCOMPARE(spy.count(), 3); QCOMPARE(spy.count(), 3);
QCOMPARE(model->room(), nullptr); QCOMPARE(model->room(), nullptr);
} }
void TimelineMessageModelTest::simpleTimeline() void MessageEventModelTest::simpleTimeline()
{ {
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-messageventmodel-sync.json"_s); auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-messageventmodel-sync.json"));
model->setRoom(room); model->setRoom(room);
QCOMPARE(model->rowCount(), 2); QCOMPARE(model->rowCount(), 2);
QCOMPARE(model->data(model->index(0), TimelineMessageModel::DelegateTypeRole), DelegateType::State); QCOMPARE(model->data(model->index(0), MessageEventModel::DelegateTypeRole), DelegateType::State);
QCOMPARE(model->data(model->index(0)), u"changed their display name to Example Changed"_s); QCOMPARE(model->data(model->index(0)), QStringLiteral("changed their display name to Example Changed"));
QCOMPARE(model->data(model->index(1)), u"<b>This is an example<br>text message</b>"_s); QCOMPARE(model->data(model->index(1)), QStringLiteral("<b>This is an example<br>text message</b>"));
QCOMPARE(model->data(model->index(1), TimelineMessageModel::DelegateTypeRole), DelegateType::Message); QCOMPARE(model->data(model->index(1), MessageEventModel::DelegateTypeRole), DelegateType::Message);
QCOMPARE(model->data(model->index(1), TimelineMessageModel::EventIdRole), u"$153456789:example.org"_s); QCOMPARE(model->data(model->index(1), MessageEventModel::EventIdRole), QStringLiteral("$153456789:example.org"));
QTest::ignoreMessage(QtWarningMsg, "Index QModelIndex(-1,-1,0x0,QObject(0x0)) is not valid (expected valid)"); QTest::ignoreMessage(QtWarningMsg, "Index QModelIndex(-1,-1,0x0,QObject(0x0)) is not valid (expected valid)");
QCOMPARE(model->data(model->index(-1)), QVariant()); QCOMPARE(model->data(model->index(-1)), QVariant());
@@ -111,37 +111,33 @@ void TimelineMessageModelTest::simpleTimeline()
QCOMPARE(model->data(model->index(model->rowCount())), QVariant()); QCOMPARE(model->data(model->index(model->rowCount())), QVariant());
} }
// Sync some events into the TimelineMessageModel's current room and don't crash. // Sync some events into the MessageEventModel's current room and don't crash.
void TimelineMessageModelTest::syncNewEvents() void MessageEventModelTest::syncNewEvents()
{ {
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s); auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
QSignalSpy spy(room, SIGNAL(aboutToAddNewMessages(Quotient::RoomEventsRange))); QSignalSpy spy(room, SIGNAL(aboutToAddNewMessages(Quotient::RoomEventsRange)));
model->setRoom(room); model->setRoom(room);
QCOMPARE(model->rowCount(), 0); QCOMPARE(model->rowCount(), 0);
room->syncNewEvents(u"test-messageventmodel-sync.json"_s); room->syncNewEvents(QLatin1String("test-messageventmodel-sync.json"));
QCOMPARE(model->rowCount(), 2); QCOMPARE(model->rowCount(), 2);
QCOMPARE(spy.count(), 1); QCOMPARE(spy.count(), 1);
} }
// Check the adding of pending events to the room doesn't cause any issues in the model. // Check the adding of pending events to the room doesn't cause any issues in the model.
void TimelineMessageModelTest::pendingEvent() void MessageEventModelTest::pendingEvent()
{ {
QSignalSpy spyInsert(model, SIGNAL(rowsInserted(const QModelIndex &, int, int))); QSignalSpy spyInsert(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)));
QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int))); QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)));
QSignalSpy spyChanged(model, SIGNAL(dataChanged(const QModelIndex, const QModelIndex, const QList<int> &))); QSignalSpy spyChanged(model, SIGNAL(dataChanged(const QModelIndex, const QModelIndex, const QList<int> &)));
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s); auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
model->setRoom(room); model->setRoom(room);
QCOMPARE(model->rowCount(), 0); QCOMPARE(model->rowCount(), 0);
#if Quotient_VERSION_MINOR > 9 auto txnId = room->postPlainText("New plain message"_ls);
auto txnId = room->postText("New plain message"_L1);
#else
auto txnId = room->postPlainText("New plain message"_L1);
#endif
QCOMPARE(model->rowCount(), 1); QCOMPARE(model->rowCount(), 1);
QCOMPARE(spyInsert.count(), 1); QCOMPARE(spyInsert.count(), 1);
@@ -149,30 +145,26 @@ void TimelineMessageModelTest::pendingEvent()
QCOMPARE(model->rowCount(), 0); QCOMPARE(model->rowCount(), 0);
QCOMPARE(spyRemove.count(), 1); QCOMPARE(spyRemove.count(), 1);
#if Quotient_VERSION_MINOR > 9 txnId = room->postPlainText("New plain message"_ls);
txnId = room->postText("New plain message"_L1);
#else
txnId = room->postPlainText("New plain message"_L1);
#endif
QCOMPARE(model->rowCount(), 1); QCOMPARE(model->rowCount(), 1);
QCOMPARE(spyInsert.count(), 2); QCOMPARE(spyInsert.count(), 2);
// We need to manually set the transaction ID of the new message as it will be // We need to manually set the transaction ID of the new message as it will be
// different every time. // different every time.
QFile testSyncFile; QFile testSyncFile;
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + u"test-pending-sync.json"_s); testSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-pending-sync.json"));
testSyncFile.open(QIODevice::ReadOnly); testSyncFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()); auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
auto root = testSyncJson.object(); auto root = testSyncJson.object();
auto timeline = root["timeline"_L1].toObject(); auto timeline = root["timeline"_ls].toObject();
auto events = timeline["events"_L1].toArray(); auto events = timeline["events"_ls].toArray();
auto firstEvent = events[0].toObject(); auto firstEvent = events[0].toObject();
firstEvent.insert("unsigned"_L1, QJsonObject{{"transaction_id"_L1, txnId}}); firstEvent.insert(QLatin1String("unsigned"), QJsonObject{{QLatin1String("transaction_id"), txnId}});
events[0] = firstEvent; events[0] = firstEvent;
timeline.insert("events"_L1, events); timeline.insert("events"_ls, events);
root.insert("timeline"_L1, timeline); root.insert("timeline"_ls, timeline);
testSyncJson.setObject(root); testSyncJson.setObject(root);
SyncRoomData roomData(u"@bob:kde.org"_s, JoinState::Join, testSyncJson.object()); SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testSyncJson.object());
room->update(std::move(roomData)); room->update(std::move(roomData));
QCOMPARE(model->rowCount(), 1); QCOMPARE(model->rowCount(), 1);
@@ -182,7 +174,7 @@ void TimelineMessageModelTest::pendingEvent()
auto isPendingChanged = false; auto isPendingChanged = false;
for (auto signal : spyChanged) { for (auto signal : spyChanged) {
auto roles = signal.at(2).toList(); auto roles = signal.at(2).toList();
if (roles.contains(TimelineMessageModel::IsPendingRole)) { if (roles.contains(MessageEventModel::IsPendingRole)) {
isPendingChanged = true; isPendingChanged = true;
} }
} }
@@ -190,33 +182,33 @@ void TimelineMessageModelTest::pendingEvent()
} }
// Make sure that the signals are disconnecting correctly when a room is switched. // Make sure that the signals are disconnecting correctly when a room is switched.
void TimelineMessageModelTest::disconnect() void MessageEventModelTest::disconnect()
{ {
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s); auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
model->setRoom(room); model->setRoom(room);
QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex &, int, int))); QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)));
model->setRoom(nullptr); model->setRoom(nullptr);
room->syncNewEvents(u"test-messageventmodel-sync.json"_s); room->syncNewEvents(QLatin1String("test-messageventmodel-sync.json"));
QCOMPARE(spy.count(), 0); QCOMPARE(spy.count(), 0);
} }
void TimelineMessageModelTest::idToRow() void MessageEventModelTest::idToRow()
{ {
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s); auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-min-sync.json"));
model->setRoom(room); model->setRoom(room);
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0); QCOMPARE(model->eventIdToRow(QStringLiteral("$153456789:example.org")), 0);
} }
void TimelineMessageModelTest::cleanup() void MessageEventModelTest::cleanup()
{ {
delete model; delete model;
model = nullptr; model = nullptr;
QCOMPARE(model, nullptr); QCOMPARE(model, nullptr);
} }
QTEST_MAIN(TimelineMessageModelTest) QTEST_MAIN(MessageEventModelTest)
#include "timelinemessagemodeltest.moc" #include "messageeventmodeltest.moc"

View File

@@ -27,8 +27,8 @@ private Q_SLOTS:
void NeoChatRoomTest::initTestCase() void NeoChatRoomTest::initTestCase()
{ {
connection = Connection::makeMockConnection(u"@bob:kde.org"_s); connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s); room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), "test-min-sync.json"_ls);
} }
void NeoChatRoomTest::eventTest() void NeoChatRoomTest::eventTest()

View File

@@ -31,8 +31,8 @@ private Q_SLOTS:
void PollHandlerTest::initTestCase() void PollHandlerTest::initTestCase()
{ {
connection = Connection::makeMockConnection(u"@bob:kde.org"_s); connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-pollhandlerstart-sync.json"_s); room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), "test-pollhandlerstart-sync.json"_ls);
} }
// Basically don't crash. // Basically don't crash.
@@ -41,32 +41,29 @@ void PollHandlerTest::nullObject()
auto pollHandler = PollHandler(); auto pollHandler = PollHandler();
QCOMPARE(pollHandler.hasEnded(), false); QCOMPARE(pollHandler.hasEnded(), false);
QCOMPARE(pollHandler.numAnswers(), 0); QCOMPARE(pollHandler.answerCount(), 0);
QCOMPARE(pollHandler.question(), QString()); QCOMPARE(pollHandler.question(), QString());
QCOMPARE(pollHandler.kind(), PollKind::Disclosed); QCOMPARE(pollHandler.options(), QJsonArray());
QCOMPARE(pollHandler.answers(), QJsonObject());
QCOMPARE(pollHandler.counts(), QJsonObject());
QCOMPARE(pollHandler.kind(), QString());
} }
void PollHandlerTest::poll() void PollHandlerTest::poll()
{ {
auto startEvent = eventCast<const PollStartEvent>(room->messageEvents().at(0).get()); auto startEvent = eventCast<const PollStartEvent>(room->messageEvents().at(0).get());
auto pollHandler = PollHandler(room, startEvent->id()); auto pollHandler = PollHandler(room, startEvent);
QList<Quotient::EventContent::Answer> options = {EventContent::Answer{"option1"_L1, "option1"_L1}, EventContent::Answer{"option2"_L1, "option2"_L1}}; auto options = QJsonArray{QJsonObject{{"id"_ls, "option1"_ls}, {"org.matrix.msc1767.text"_ls, "option1"_ls}},
QJsonObject{{"id"_ls, "option2"_ls}, {"org.matrix.msc1767.text"_ls, "option2"_ls}}};
const auto answer0 = pollHandler.answerAtRow(0);
const auto answer1 = pollHandler.answerAtRow(1);
QCOMPARE(pollHandler.hasEnded(), false); QCOMPARE(pollHandler.hasEnded(), false);
QCOMPARE(pollHandler.numAnswers(), 2); QCOMPARE(pollHandler.answerCount(), 0);
QCOMPARE(pollHandler.question(), u"test"_s); QCOMPARE(pollHandler.question(), QStringLiteral("test"));
QCOMPARE(answer0.id, "option1"_L1); QCOMPARE(pollHandler.options(), options);
QCOMPARE(answer1.id, "option2"_L1); QCOMPARE(pollHandler.answers(), QJsonObject());
QCOMPARE(answer0.text, "option1text"_L1); QCOMPARE(pollHandler.counts(), QJsonObject());
QCOMPARE(answer1.text, "option2text"_L1); QCOMPARE(pollHandler.kind(), QStringLiteral("org.matrix.msc3381.poll.disclosed"));
QCOMPARE(pollHandler.answerCountAtId(answer0.id), 0);
QCOMPARE(pollHandler.answerCountAtId(answer1.id), 0);
QCOMPARE(pollHandler.checkMemberSelectedId(connection->userId(), answer0.id), false);
QCOMPARE(pollHandler.checkMemberSelectedId(connection->userId(), answer1.id), false);
QCOMPARE(pollHandler.kind(), PollKind::Undisclosed);
} }
QTEST_GUILESS_MAIN(PollHandlerTest) QTEST_GUILESS_MAIN(PollHandlerTest)

View File

@@ -9,7 +9,6 @@
#include <Quotient/events/roommessageevent.h> #include <Quotient/events/roommessageevent.h>
#include "models/messagecontentmodel.h"
#include "testutils.h" #include "testutils.h"
using namespace Quotient; using namespace Quotient;
@@ -21,50 +20,59 @@ class ReactionModelTest : public QObject
private: private:
Connection *connection = nullptr; Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr; TestUtils::TestRoom *room = nullptr;
MessageContentModel *parentModel;
private Q_SLOTS: private Q_SLOTS:
void initTestCase(); void initTestCase();
void nullModel();
void basicReaction(); void basicReaction();
void newReaction(); void newReaction();
}; };
void ReactionModelTest::initTestCase() void ReactionModelTest::initTestCase()
{ {
connection = Connection::makeMockConnection(u"@bob:kde.org"_s); connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s); room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-reactionmodel-sync.json"));
parentModel = new MessageContentModel(room, "123456"_L1); }
void ReactionModelTest::nullModel()
{
auto model = ReactionModel(nullptr, nullptr);
QCOMPARE(model.rowCount(), 0);
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QVariant());
} }
void ReactionModelTest::basicReaction() void ReactionModelTest::basicReaction()
{ {
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get()); auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto model = ReactionModel(parentModel, event->id(), room); auto model = ReactionModel(event, room);
QCOMPARE(model.rowCount(), 1); QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), u"<span style=\"font-family: 'emoji';\">👍</span>"_s); QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QStringLiteral("<span style=\"font-family: 'emoji';\">👍</span>"));
QCOMPARE(model.data(model.index(0), ReactionModel::ReactionRole), u"👍"_s); QCOMPARE(model.data(model.index(0), ReactionModel::ReactionRole), QStringLiteral("👍"));
QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole), u"Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"_s); QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole),
QStringLiteral("Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalMember), false); QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalMember), false);
} }
void ReactionModelTest::newReaction() void ReactionModelTest::newReaction()
{ {
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get()); auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto model = new ReactionModel(parentModel, event->id(), room); auto model = new ReactionModel(event, room);
QCOMPARE(model->rowCount(), 1); QCOMPARE(model->rowCount(), 1);
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole), u"Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"_s); QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
QStringLiteral("Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
QSignalSpy spy(model, SIGNAL(modelReset())); QSignalSpy spy(model, SIGNAL(modelReset()));
room->syncNewEvents(u"test-reactionmodel-extra-sync.json"_s); room->syncNewEvents(QLatin1String("test-reactionmodel-extra-sync.json"));
QCOMPARE(model->rowCount(), 2); QCOMPARE(model->rowCount(), 2);
QCOMPARE(spy.count(), 2); // Once for each of the 2 new reactions. QCOMPARE(spy.count(), 2); // Once for each of the 2 new reactions.
QCOMPARE(model->data(model->index(1), ReactionModel::ReactionRole), u"😆"_s); QCOMPARE(model->data(model->index(1), ReactionModel::ReactionRole), QStringLiteral("😆"));
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole), QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
u"Alice Margatroid and Bob reacted with <span style=\"font-family: 'emoji';\">👍</span>"_s); QStringLiteral("Alice Margatroid and Bob reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
delete model; delete model;
} }

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ public:
{ {
if (!syncFileName.isEmpty()) { if (!syncFileName.isEmpty()) {
QFile testSyncFile; QFile testSyncFile;
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName); testSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + syncFileName);
testSyncFile.open(QIODevice::ReadOnly); testSyncFile.open(QIODevice::ReadOnly);
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()); const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object()); Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
@@ -45,7 +45,7 @@ inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFile
{ {
if (!eventFileName.isEmpty()) { if (!eventFileName.isEmpty()) {
QFile testEventFile; QFile testEventFile;
testEventFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + eventFileName); testEventFile.setFileName(QLatin1String(DATA_DIR) + u'/' + eventFileName);
testEventFile.open(QIODevice::ReadOnly); testEventFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object(); auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
return Quotient::loadEvent<EventT>(testSyncJson); return Quotient::loadEvent<EventT>(testSyncJson);

View File

@@ -8,12 +8,13 @@
#include <Quotient/quotient_common.h> #include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h> #include <Quotient/syncdata.h>
#include <qnamespace.h>
#include <Kirigami/Platform/PlatformTheme>
#include "enums/messagecomponenttype.h" #include "enums/messagecomponenttype.h"
#include "models/customemojimodel.h" #include "models/customemojimodel.h"
#include "models/messagecontentmodel.h"
#include "neochatconnection.h" #include "neochatconnection.h"
#include "utils.h"
#include "testutils.h" #include "testutils.h"
@@ -44,10 +45,7 @@ private Q_SLOTS:
void sendCustomEmoji(); void sendCustomEmoji();
void sendCustomEmojiCode_data(); void sendCustomEmojiCode_data();
void sendCustomEmojiCode(); void sendCustomEmojiCode();
void sendCustomTags_data();
void sendCustomTags();
void receiveSpacelessSelfClosingTag();
void receiveStripReply(); void receiveStripReply();
void receivePlainTextIn(); void receivePlainTextIn();
@@ -61,13 +59,11 @@ private Q_SLOTS:
void receiveRichStrikethrough(); void receiveRichStrikethrough();
void receiveRichtextIn(); void receiveRichtextIn();
void receiveRichMxcUrl(); void receiveRichMxcUrl();
void receiveRichPlainUrl_data();
void receiveRichPlainUrl(); void receiveRichPlainUrl();
void receiveRichEdited_data(); void receiveRichEdited_data();
void receiveRichEdited(); void receiveRichEdited();
void receiveLineSeparator(); void receiveLineSeparator();
void receiveRichCodeUrl(); void receiveRichCodeUrl();
void receiveRichColor();
void componentOutput_data(); void componentOutput_data();
void componentOutput(); void componentOutput();
@@ -75,25 +71,25 @@ private Q_SLOTS:
void TextHandlerTest::initTestCase() void TextHandlerTest::initTestCase()
{ {
connection = Connection::makeMockConnection(u"@bob:kde.org"_s); connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
connection->setAccountData(u"im.ponies.user_emotes"_s, connection->setAccountData("im.ponies.user_emotes"_ls,
QJsonObject{{"images"_L1, QJsonObject{{"images"_ls,
QJsonObject{{"test"_L1, QJsonObject{{"test"_ls,
QJsonObject{{"body"_L1, "Test custom emoji"_L1}, QJsonObject{{"body"_ls, "Test custom emoji"_ls},
{"url"_L1, "mxc://example.org/test"_L1}, {"url"_ls, "mxc://example.org/test"_ls},
{"usage"_L1, QJsonArray{"emoticon"_L1}}}}}}}); {"usage"_ls, QJsonArray{"emoticon"_ls}}}}}}});
CustomEmojiModel::instance().setConnection(static_cast<NeoChatConnection *>(connection)); CustomEmojiModel::instance().setConnection(static_cast<NeoChatConnection *>(connection));
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-texthandler-sync.json"_s); room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-texthandler-sync.json"));
} }
void TextHandlerTest::allowedAttributes() void TextHandlerTest::allowedAttributes()
{ {
const QString testInputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s; const QString testInputString1 = QStringLiteral("<p><span data-mx-spoiler><font color=#FFFFFF>Test</font><span></p>");
const QString testOutputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s; const QString testOutputString1 = QStringLiteral("<p><span data-mx-spoiler><font color=#FFFFFF>Test</font><span></p>");
// Handle urls where the href has either single (') or double (") quotes. // Handle urls where the href has either single (') or double (") quotes.
const QString testInputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s; const QString testInputString2 = QStringLiteral("<p><a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a></p>");
const QString testOutputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s; const QString testOutputString2 = QStringLiteral("<p><a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a></p>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString1); testTextHandler.setData(testInputString1);
@@ -108,8 +104,8 @@ void TextHandlerTest::allowedAttributes()
void TextHandlerTest::stripDisallowedTags() void TextHandlerTest::stripDisallowedTags()
{ {
const QString testInputString = u"<p>Allowed</p> <span>Allowed</span> <body>Disallowed</body>"_s; const QString testInputString = QStringLiteral("<p>Allowed</p> <span>Allowed</span> <body>Disallowed</body>");
const QString testOutputString = u"<p>Allowed</p> <span>Allowed</span> Disallowed"_s; const QString testOutputString = QStringLiteral("<p>Allowed</p> <span>Allowed</span> Disallowed");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -120,8 +116,8 @@ void TextHandlerTest::stripDisallowedTags()
void TextHandlerTest::stripDisallowedAttributes() void TextHandlerTest::stripDisallowedAttributes()
{ {
const QString testInputString = u"<p style=\"font-size:50px;\" color=#FFFFFF>Test</p>"_s; const QString testInputString = QStringLiteral("<p style=\"font-size:50px;\" color=#FFFFFF>Test</p>");
const QString testOutputString = u"Test"_s; const QString testOutputString = QStringLiteral("<p>Test</p>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -136,8 +132,8 @@ void TextHandlerTest::stripDisallowedAttributes()
*/ */
void TextHandlerTest::emptyCodeTags() void TextHandlerTest::emptyCodeTags()
{ {
const QString testInputString = u"<pre><code></code></pre>"_s; const QString testInputString = QStringLiteral("<pre><code></code></pre>");
const QString testOutputString = u"<pre><code></code></pre>"_s; const QString testOutputString = QStringLiteral("<pre><code></code></pre>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -148,8 +144,8 @@ void TextHandlerTest::emptyCodeTags()
void TextHandlerTest::sendSimpleStringCase() void TextHandlerTest::sendSimpleStringCase()
{ {
const QString testInputString = u"This data should just be left alone."_s; const QString testInputString = QStringLiteral("This data should just be put in a paragraph.");
const QString testOutputString = u"This data should just be left alone."_s; const QString testOutputString = QStringLiteral("<p>This data should just be put in a paragraph.</p>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -159,10 +155,11 @@ void TextHandlerTest::sendSimpleStringCase()
void TextHandlerTest::sendSingleParaMarkup() void TextHandlerTest::sendSingleParaMarkup()
{ {
const QString testInputString = const QString testInputString = QStringLiteral(
u"Text para with **bold**, *italic*, [link](https://kde.org), ![image](mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e), `inline code`."_s; "Text para with **bold**, *italic*, [link](https://kde.org), ![image](mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e), `inline code`.");
const QString testOutputString = const QString testOutputString = QStringLiteral(
u"Text para with <strong>bold</strong>, <em>italic</em>, <a href=\"https://kde.org\">link</a>, <img src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\">, <code>inline code</code>."_s; "<p>Text para with <strong>bold</strong>, <em>italic</em>, <a href=\"https://kde.org\">link</a>, <img "
"src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\">, <code>inline code</code>.</p>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -173,9 +170,11 @@ void TextHandlerTest::sendSingleParaMarkup()
void TextHandlerTest::sendMultipleSectionMarkup() void TextHandlerTest::sendMultipleSectionMarkup()
{ {
const QString testInputString = const QString testInputString =
u"Text para\n> blockquote\n* List 1\n* List 2\n1. one\n2. two\n# Heading 1\n## Heading 2\nhorizontal rule\n\n---\n```\ncodeblock\n```"_s; QStringLiteral("Text para\n> blockquote\n* List 1\n* List 2\n1. one\n2. two\n# Heading 1\n## Heading 2\nhorizontal rule\n\n---\n```\ncodeblock\n```");
const QString testOutputString = const QString testOutputString = QStringLiteral(
u"<p>Text para</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>\n<ul>\n<li>List 1</li>\n<li>List 2</li>\n</ul>\n<ol>\n<li>one</li>\n<li>two</li>\n</ol>\n<h1>Heading 1</h1>\n<h2>Heading 2</h2>\n<p>horizontal rule</p>\n<hr>\n<pre><code>codeblock\n</code></pre>"_s; "<p>Text para</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>\n<ul>\n<li>List 1</li>\n<li>List "
"2</li>\n</ul>\n<ol>\n<li>one</li>\n<li>two</li>\n</ol>\n<h1>Heading 1</h1>\n<h2>Heading 2</h2>\n<p>horizontal "
"rule</p>\n<hr>\n<pre><code>codeblock\n</code></pre>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -185,8 +184,8 @@ void TextHandlerTest::sendMultipleSectionMarkup()
void TextHandlerTest::sendBadLinks() void TextHandlerTest::sendBadLinks()
{ {
const QString testInputString = u"[link](kde.org), ![image](https://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e)"_s; const QString testInputString = QStringLiteral("[link](kde.org), ![image](https://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e)");
const QString testOutputString = u"<a>link</a>, <img alt=\"image\">"_s; const QString testOutputString = QStringLiteral("<p><a>link</a>, <img alt=\"image\"></p>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -199,9 +198,9 @@ void TextHandlerTest::sendBadLinks()
*/ */
void TextHandlerTest::sendEscapeCode() void TextHandlerTest::sendEscapeCode()
{ {
const QString testInputString = u"```\n<p>Test <span style=\"font-size:50px;\">some</span> code</p>\n```"_s; const QString testInputString = QStringLiteral("```\n<p>Test <span style=\"font-size:50px;\">some</span> code</p>\n```");
const QString testOutputString = const QString testOutputString =
u"<pre><code>&lt;p&gt;Test &lt;span style=&quot;font-size:50px;&quot;&gt;some&lt;/span&gt; code&lt;/p&gt;\n</code></pre>"_s; QStringLiteral("<pre><code>&lt;p&gt;Test &lt;span style=&quot;font-size:50px;&quot;&gt;some&lt;/span&gt; code&lt;/p&gt;\n</code></pre>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -211,8 +210,8 @@ void TextHandlerTest::sendEscapeCode()
void TextHandlerTest::sendCodeClass() void TextHandlerTest::sendCodeClass()
{ {
const QString testInputString = u"```html\nsome code\n```\n<pre><code class=\"code-underline\">some more code</code></pre>"_s; const QString testInputString = QStringLiteral("```html\nsome code\n```\n<pre><code class=\"code-underline\">some more code</code></pre>");
const QString testOutputString = u"<pre><code class=\"language-html\">some code\n</code></pre>\n<pre><code>some more code</code></pre>"_s; const QString testOutputString = QStringLiteral("<pre><code class=\"language-html\">some code\n</code></pre>\n<pre><code>some more code</code></pre>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -222,9 +221,9 @@ void TextHandlerTest::sendCodeClass()
void TextHandlerTest::sendCustomEmoji() void TextHandlerTest::sendCustomEmoji()
{ {
const QString testInputString = u":test:"_s; const QString testInputString = QStringLiteral(":test:");
const QString testOutputString = const QString testOutputString = QStringLiteral(
u"<img data-mx-emoticon=\"\" src=\"mxc://example.org/test\" alt=\":test:\" title=\":test:\" height=\"32\" vertical-align=\"middle\" />"_s; "<p><img data-mx-emoticon=\"\" src=\"mxc://example.org/test\" alt=\":test:\" title=\":test:\" height=\"32\" vertical-align=\"middle\" /></p>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -237,8 +236,8 @@ void TextHandlerTest::sendCustomEmojiCode_data()
QTest::addColumn<QString>("testInputString"); QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString"); QTest::addColumn<QString>("testOutputString");
QTest::newRow("inline") << u"`:test:`"_s << u"<code>:test:</code>"_s; QTest::newRow("inline") << QStringLiteral("`:test:`") << QStringLiteral("<p><code>:test:</code></p>");
QTest::newRow("block") << u"```\n:test:\n```"_s << u"<pre><code>:test:\n</code></pre>"_s; QTest::newRow("block") << QStringLiteral("```\n:test:\n```") << QStringLiteral("<pre><code>:test:\n</code></pre>");
} }
// Custom emojis in code blocks should be left alone. // Custom emojis in code blocks should be left alone.
@@ -253,58 +252,12 @@ void TextHandlerTest::sendCustomEmojiCode()
QCOMPARE(testTextHandler.handleSendText(), testOutputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString);
} }
void TextHandlerTest::sendCustomTags_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString");
// spoiler
QTest::newRow("incomplete spoiler") << u"||test"_s << u"||test"_s;
QTest::newRow("complete spoiler") << u"||test||"_s << u"<span data-mx-spoiler>test</span>"_s;
QTest::newRow("multiple spoiler") << u"||apple||banana||pear||"_s << u"<span data-mx-spoiler>apple</span>banana<span data-mx-spoiler>pear</span>"_s;
QTest::newRow("inside code block spoiler") << u"```||apple||```"_s << u"<code>||apple||</code>"_s;
QTest::newRow("outside code block spoiler") << u"||apple|| ```||banana||``` ||pear||"_s
<< u"<span data-mx-spoiler>apple</span> <code>||banana||</code> <span data-mx-spoiler>pear</span>"_s;
QTest::newRow("complex spoiler") << u"Between `formFactor == Horizontal||Vertical` and `location == top||left||bottom||right`"_s
<< u"Between <code>formFactor == Horizontal||Vertical</code> and <code>location == top||left||bottom||right</code>"_s;
// strikethrough
QTest::newRow("incomplete strikethrough") << u"~~test"_s << u"~~test"_s;
QTest::newRow("complete strikethrough") << u"~~test~~"_s << u"<del>test</del>"_s;
QTest::newRow("inside code block strikethrough") << u"```~~apple~~```"_s << u"<code>~~apple~~</code>"_s;
QTest::newRow("outside code block strikethrough") << u"~~apple~~ ```~~banana~~``` ~~pear~~"_s
<< u"<del>apple</del> <code>~~banana~~</code> <del>pear</del>"_s;
}
void TextHandlerTest::sendCustomTags()
{
QFETCH(QString, testInputString);
QFETCH(QString, testOutputString);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
}
void TextHandlerTest::receiveSpacelessSelfClosingTag()
{
const QString testInputString = u"Test...<br/>...ing"_s;
const QString testRichOutputString = u"Test...<br/>...ing"_s;
const QString testPlainOutputString = u"Test...\n...ing"_s;
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.handleRecieveRichText(), testRichOutputString);
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testPlainOutputString);
}
void TextHandlerTest::receiveStripReply() void TextHandlerTest::receiveStripReply()
{ {
const QString testInputString = const QString testInputString = QStringLiteral(
u"<mx-reply><blockquote><a href=\"https://matrix.to/#/!somewhere:example.org/$event:example.org\">In reply to</a><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a><br />Message replied to.</blockquote></mx-reply>Reply message."_s; "<mx-reply><blockquote><a href=\"https://matrix.to/#/!somewhere:example.org/$event:example.org\">In reply to</a><a "
const QString testOutputString = u"Reply message."_s; "href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a><br />Message replied to.</blockquote></mx-reply>Reply message.");
const QString testOutputString = QStringLiteral("Reply message.");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -318,10 +271,9 @@ void TextHandlerTest::receiveRichInPlainOut_data()
QTest::addColumn<QString>("testInputString"); QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString"); QTest::addColumn<QString>("testOutputString");
QTest::newRow("ampersand") << u"a &amp; b"_s << u"a & b"_s; QTest::newRow("ampersand") << QStringLiteral("a &amp; b") << QStringLiteral("a & b");
QTest::newRow("quote") << u"&quot;a and b&quot;"_s << u"\"a and b\""_s; QTest::newRow("quote") << QStringLiteral("&quot;a and b&quot;") << QStringLiteral("\"a and b\"");
QTest::newRow("new line") << u"new<br>line"_s << u"new\nline"_s; QTest::newRow("new line") << QStringLiteral("new<br>line") << QStringLiteral("new\nline");
QTest::newRow("unescape") << u"can&#x27;t"_s << u"can't"_s;
} }
void TextHandlerTest::receiveRichInPlainOut() void TextHandlerTest::receiveRichInPlainOut()
@@ -337,13 +289,13 @@ void TextHandlerTest::receiveRichInPlainOut()
void TextHandlerTest::receivePlainTextIn() void TextHandlerTest::receivePlainTextIn()
{ {
const QString testInputString = u"<plain text in tag bracket>\nTest link https://kde.org."_s; const QString testInputString = QStringLiteral("<plain text in tag bracket>\nTest link https://kde.org.");
const QString testOutputStringRich = u"&lt;plain text in tag bracket&gt;<br>Test link <a href=\"https://kde.org\">https://kde.org</a>."_s; const QString testOutputStringRich = QStringLiteral("&lt;plain text in tag bracket&gt;<br>Test link <a href=\"https://kde.org\">https://kde.org</a>.");
QString testOutputStringPlain = u"<plain text in tag bracket>\nTest link https://kde.org."_s; QString testOutputStringPlain = QStringLiteral("<plain text in tag bracket>\nTest link https://kde.org.");
// Make sure quotes are maintained in a plain string. // Make sure quotes are maintained in a plain string.
const QString testInputString2 = u"last line is \"Time to switch to a new topic.\""_s; const QString testInputString2 = QStringLiteral("last line is \"Time to switch to a new topic.\"");
const QString testOutputString2 = u"last line is \"Time to switch to a new topic.\""_s; const QString testOutputString2 = QStringLiteral("last line is \"Time to switch to a new topic.\"");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -358,12 +310,12 @@ void TextHandlerTest::receivePlainTextIn()
void TextHandlerTest::receiveStripNewlines() void TextHandlerTest::receiveStripNewlines()
{ {
const QString testInputStringPlain = u"Test\nmany\nnew\nlines."_s; const QString testInputStringPlain = QStringLiteral("Test\nmany\nnew\nlines.");
const QString testInputStringRich = u"Test<br>many<br />new<br>lines."_s; const QString testInputStringRich = QStringLiteral("Test<br>many<br />new<br>lines.");
const QString testOutputString = u"Test many new lines."_s; const QString testOutputString = QStringLiteral("Test many new lines.");
const QString testInputStringPlain2 = u"* List\n* Items"_s; const QString testInputStringPlain2 = QStringLiteral("* List\n* Items");
const QString testOutputString2 = u"List Items"_s; const QString testOutputString2 = QStringLiteral("List Items");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputStringPlain); testTextHandler.setData(testInputStringPlain);
@@ -385,8 +337,8 @@ void TextHandlerTest::receiveStripNewlines()
*/ */
void TextHandlerTest::receivePlainStripHtml() void TextHandlerTest::receivePlainStripHtml()
{ {
const QString testInputString = u"<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>"_s; const QString testInputString = QStringLiteral("<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>");
const QString testOutputString = u"Test Some code <strong>with tags</strong>"_s; const QString testOutputString = QStringLiteral("Test Some code <strong>with tags</strong>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -396,8 +348,8 @@ void TextHandlerTest::receivePlainStripHtml()
void TextHandlerTest::receivePlainStripMarkup() void TextHandlerTest::receivePlainStripMarkup()
{ {
const QString testInputString = u"**bold** `<p>inline code</p>` *italic*"_s; const QString testInputString = QStringLiteral("**bold** `<p>inline code</p>` *italic*");
const QString testOutputString = u"bold <p>inline code</p> italic"_s; const QString testOutputString = QStringLiteral("bold <p>inline code</p> italic");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -407,8 +359,8 @@ void TextHandlerTest::receivePlainStripMarkup()
void TextHandlerTest::receiveRichUserPill() void TextHandlerTest::receiveRichUserPill()
{ {
const QString testInputString = u"<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>"_s; const QString testInputString = QStringLiteral("<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>");
const QString testOutputString = u"<b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b>"_s; const QString testOutputString = QStringLiteral("<p><b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b></p>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -418,8 +370,8 @@ void TextHandlerTest::receiveRichUserPill()
void TextHandlerTest::receiveRichStrikethrough() void TextHandlerTest::receiveRichStrikethrough()
{ {
const QString testInputString = u"<p><del>Test</del></p>"_s; const QString testInputString = QStringLiteral("<p><del>Test</del></p>");
const QString testOutputString = u"<s>Test</s>"_s; const QString testOutputString = QStringLiteral("<p><s>Test</s></p>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -429,8 +381,8 @@ void TextHandlerTest::receiveRichStrikethrough()
void TextHandlerTest::receiveRichtextIn() void TextHandlerTest::receiveRichtextIn()
{ {
const QString testInputString = u"<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>"_s; const QString testInputString = QStringLiteral("<p>Test</p> <pre><code>Some code <strong>with tags</strong></code></pre>");
const QString testOutputString = u"<p>Test</p> <pre><code>Some code &lt;strong&gt;with tags&lt;/strong&gt;</code></pre>"_s; const QString testOutputString = QStringLiteral("<p>Test</p> <pre><code>Some code &lt;strong&gt;with tags&lt;/strong&gt;</code></pre>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -440,10 +392,15 @@ void TextHandlerTest::receiveRichtextIn()
void TextHandlerTest::receiveRichMxcUrl() void TextHandlerTest::receiveRichMxcUrl()
{ {
const QString testInputString = const QString testInputString = QStringLiteral(
u"<img src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\"><img src=\"mxc://kde.org/34c3464b3a1bd7f55af2d559e07d2c773c430e73\" alt=\"image\">"_s; "<img src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e\" alt=\"image\"><img src=\"mxc://kde.org/34c3464b3a1bd7f55af2d559e07d2c773c430e73\" "
const QString testOutputString = "alt=\"image\">");
u"<img src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e?user_id=@bob:kde.org&room_id=%23myroom:kde.org&event_id=$143273582443PhrSn:example.org\" alt=\"image\"><img src=\"mxc://kde.org/34c3464b3a1bd7f55af2d559e07d2c773c430e73?user_id=@bob:kde.org&room_id=%23myroom:kde.org&event_id=$143273582443PhrSn:example.org\" alt=\"image\">"_s; const QString testOutputString = QStringLiteral(
"<img "
"src=\"mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e?user_id=@bob:kde.org&room_id=%23myroom:kde.org&event_id=$143273582443PhrSn:example.org\" "
"alt=\"image\"><img "
"src=\"mxc://kde.org/34c3464b3a1bd7f55af2d559e07d2c773c430e73?user_id=@bob:kde.org&room_id=%23myroom:kde.org&event_id=$143273582443PhrSn:example.org\" "
"alt=\"image\">");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
@@ -451,32 +408,6 @@ void TextHandlerTest::receiveRichMxcUrl()
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
} }
void TextHandlerTest::receiveRichPlainUrl_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
// This is an actual link that caused trouble which is why it's so long. Keeping
// so we can confirm consistent behaviour for complex urls.
QTest::addRow("link 1")
<< u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s
<< u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was broken into 3 but needs to
// be just single link.
QTest::addRow("link 2")
<< u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s
<< u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
QTest::addRow("email") << uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
<< uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
QTest::addRow("mxid")
<< u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s
<< u"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>"_s;
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s << u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
}
/** /**
* For when your rich input string has a plain text url left in. * For when your rich input string has a plain text url left in.
* *
@@ -485,13 +416,51 @@ void TextHandlerTest::receiveRichPlainUrl_data()
*/ */
void TextHandlerTest::receiveRichPlainUrl() void TextHandlerTest::receiveRichPlainUrl()
{ {
QFETCH(QString, input); // This is an actual link that caused trouble which is why it's so long. Keeping
QFETCH(QString, output); // so we can confirm consistent behaviour for complex urls.
const QString testInputStringLink1 = QStringLiteral(
"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im "
"<a "
"href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/"
"$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>");
const QString testOutputStringLink1 = QStringLiteral(
"<a "
"href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/"
"$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">https://matrix.to/#/"
"!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a "
"href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/"
"$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>");
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was been broken into 3 but needs to
// be just single link.
const QString testInputStringLink2 = QStringLiteral("https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/");
const QString testOutputStringLink2 = QStringLiteral(
"<a "
"href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/"
"CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>");
QString testInputStringEmail = QStringLiteral(R"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)");
QString testOutputStringEmail =
QStringLiteral(R"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)");
QString testInputStringMxId = QStringLiteral("@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>");
QString testOutputStringMxId = QStringLiteral(
"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(input); testTextHandler.setData(testInputStringLink1);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), output); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink1);
testTextHandler.setData(testInputStringLink2);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink2);
testTextHandler.setData(testInputStringEmail);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringEmail);
testTextHandler.setData(testInputStringMxId);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
} }
void TextHandlerTest::receiveRichEdited_data() void TextHandlerTest::receiveRichEdited_data()
@@ -499,12 +468,9 @@ void TextHandlerTest::receiveRichEdited_data()
QTest::addColumn<QString>("testInputString"); QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString"); QTest::addColumn<QString>("testOutputString");
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true)); QTest::newRow("basic") << QStringLiteral("Edited") << QStringLiteral("Edited <span style=\"color:#000000\">(edited)</span>");
QTest::newRow("multiple paragraphs") << QStringLiteral("<p>Edited</p>\n<p>Edited</p>")
QTest::newRow("basic") << u"Edited"_s << u"Edited <span style=\"color:%1\">(edited)</span>"_s.arg(theme ? theme->disabledTextColor().name() : u"#000000"_s); << QStringLiteral("<p>Edited</p>\n<p>Edited <span style=\"color:#000000\">(edited)</span></p>");
QTest::newRow("multiple paragraphs") << u"<p>Edited</p>\n<p>Edited</p>"_s
<< u"<p>Edited</p>\n<p>Edited <span style=\"color:%1\">(edited)</span></p>"_s.arg(
theme ? theme->disabledTextColor().name() : u"#000000"_s);
} }
void TextHandlerTest::receiveRichEdited() void TextHandlerTest::receiveRichEdited()
@@ -521,70 +487,61 @@ void TextHandlerTest::receiveRichEdited()
void TextHandlerTest::receiveLineSeparator() void TextHandlerTest::receiveLineSeparator()
{ {
auto text = u"foo\u2028bar"_s; auto text = QStringLiteral("foo\u2028bar");
TextHandler textHandler; TextHandler textHandler;
textHandler.setData(text); textHandler.setData(text);
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), u"foo bar"_s); QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
} }
void TextHandlerTest::receiveRichCodeUrl() void TextHandlerTest::receiveRichCodeUrl()
{ {
auto input = u"<code>https://kde.org</code>"_s; auto input = QStringLiteral("<code>https://kde.org</code>");
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(input); testTextHandler.setData(input);
QCOMPARE(testTextHandler.handleRecieveRichText(), input); QCOMPARE(testTextHandler.handleRecieveRichText(), input);
} }
void TextHandlerTest::receiveRichColor()
{
const QString testInputString =
u"<span data-mx-color=\"#ff00be\">¯</span><span data-mx-color=\"#ff3b1d\">\\</span><span data-mx-color=\"#ffa600\">_</span><span data-mx-color=\"#64d200\">(</span><span data-mx-color=\"#00e261\">ツ</span><span data-mx-color=\"#00e7ff\">)</span><span data-mx-color=\"#00e1ff\">_</span><span data-mx-color=\"#00bdff\">/</span><span data-mx-color=\"#ff60ff\">¯</span>"_s;
const QString testOutputString =
u"<span style=\"color: #ff00be;\">¯</span><span style=\"color: #ff3b1d;\">\\</span><span style=\"color: #ffa600;\">_</span><span style=\"color: #64d200;\">(</span><span style=\"color: #00e261;\">ツ</span><span style=\"color: #00e7ff;\">)</span><span style=\"color: #00e1ff;\">_</span><span style=\"color: #00bdff;\">/</span><span style=\"color: #ff60ff;\">¯</span>"_s;
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
}
void TextHandlerTest::componentOutput_data() void TextHandlerTest::componentOutput_data()
{ {
QTest::addColumn<QString>("testInputString"); QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<MessageComponent>>("testOutputComponents"); QTest::addColumn<QList<MessageComponent>>("testOutputComponents");
QTest::newRow("multiple paragraphs") << u"<p>Text</p>\n<p>Text</p>"_s QTest::newRow("multiple paragraphs") << QStringLiteral("<p>Text</p>\n<p>Text</p>")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, << QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Text, u"Text"_s, {}}}; MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}};
QTest::newRow("code") << u"<p>Text</p>\n<pre><code class=\"language-html\">Some code\n</code></pre>"_s QTest::newRow("code") << QStringLiteral("<p>Text</p>\n<pre><code class=\"language-html\">Some code\n</code></pre>")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, << QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Code, u"Some code"_s, QVariantMap{{u"class"_s, u"html"_s}}}}; MessageComponent{MessageComponentType::Code,
QTest::newRow("quote") << u"<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>"_s QStringLiteral("Some code"),
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, QVariantMap{{QStringLiteral("class"), QStringLiteral("html")}}}};
MessageComponent{MessageComponentType::Quote, u"“blockquote”"_s, {}}}; QTest::newRow("quote") << QStringLiteral("<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>")
QTest::newRow("multiple paragraph quote") << u"<blockquote>\n<p>blockquote</p>\n<p>next paragraph</p>\n</blockquote>"_s << QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Quote, QStringLiteral("\"blockquote\""), {}}};
QTest::newRow("no tag first paragraph") << QStringLiteral("Text\n<p>Text</p>")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}};
QTest::newRow("no tag last paragraph") << QStringLiteral("<p>Text</p>\nText")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}};
QTest::newRow("inline code") << QStringLiteral("<p><code>https://kde.org</code></p>\n<p>Text</p>")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("<code>https://kde.org</code>"), {}},
MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}}};
QTest::newRow("inline code single block") << QStringLiteral("<code>https://kde.org</code>")
<< QList<MessageComponent>{ << QList<MessageComponent>{
MessageComponent{MessageComponentType::Quote, u"<p>“blockquote</p>\n<p>next paragraph”</p>"_s, {}}}; MessageComponent{MessageComponentType::Text, QStringLiteral("<code>https://kde.org</code>"), {}}};
QTest::newRow("no tag first paragraph") << u"Text\n<p>Text</p>"_s
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}},
MessageComponent{MessageComponentType::Text, u"Text"_s, {}}};
QTest::newRow("no tag last paragraph") << u"<p>Text</p>\nText"_s
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}},
MessageComponent{MessageComponentType::Text, u"Text"_s, {}}};
QTest::newRow("inline code") << u"<p><code>https://kde.org</code></p>\n<p>Text</p>"_s
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"<code>https://kde.org</code>"_s, {}},
MessageComponent{MessageComponentType::Text, u"Text"_s, {}}};
QTest::newRow("inline code single block") << u"<code>https://kde.org</code>"_s
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"<code>https://kde.org</code>"_s, {}}};
QTest::newRow("long start tag") QTest::newRow("long start tag")
<< u"Ah, you mean something like<br/><pre data-md=\"```\"><code class=\"language-qml\"># main.qml\nimport CustomQml\n...\nControls.TextField { id: someField }\nCustomQml {\n someTextProperty: someField.text\n}\n</code></pre>Sure you can, it's still local to the same file where you defined the id"_s << QStringLiteral(
"Ah, you mean something like<br/><pre data-md=\"```\"><code class=\"language-qml\"># main.qml\nimport CustomQml\n...\nControls.TextField { id: "
"someField }\nCustomQml {\n someTextProperty: someField.text\n}\n</code></pre>Sure you can, it's still local to the same file where you "
"defined the id")
<< QList<MessageComponent>{ << QList<MessageComponent>{
MessageComponent{MessageComponentType::Text, u"Ah, you mean something like<br/>"_s, {}}, MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like"), {}},
MessageComponent{ MessageComponent{
MessageComponentType::Code, MessageComponentType::Code,
u"# main.qml\nimport CustomQml\n...\nControls.TextField { id: someField }\nCustomQml {\n someTextProperty: someField.text\n}"_s, QStringLiteral(
QVariantMap{{u"class"_s, u"qml"_s}}}, "# main.qml\nimport CustomQml\n...\nControls.TextField { id: someField }\nCustomQml {\n someTextProperty: someField.text\n}"),
MessageComponent{MessageComponentType::Text, u"Sure you can, it's still local to the same file where you defined the id"_s, {}}}; QVariantMap{{QStringLiteral("class"), QStringLiteral("qml")}}},
MessageComponent{MessageComponentType::Text, QStringLiteral("Sure you can, it's still local to the same file where you defined the id"), {}}};
} }
void TextHandlerTest::componentOutput() void TextHandlerTest::componentOutput()

View File

@@ -17,6 +17,7 @@ class WindowControllerTest : public QObject
private Q_SLOTS: private Q_SLOTS:
void nullWindow(); void nullWindow();
void geometry();
void showAndRaise(); void showAndRaise();
void toggle(); void toggle();
@@ -29,10 +30,32 @@ void WindowControllerTest::nullWindow()
auto &instance = WindowController::instance(); auto &instance = WindowController::instance();
QCOMPARE(instance.window(), nullptr); QCOMPARE(instance.window(), nullptr);
instance.restoreGeometry();
instance.saveGeometry();
instance.showAndRaiseWindow({}); instance.showAndRaiseWindow({});
instance.toggleWindow(); instance.toggleWindow();
} }
void WindowControllerTest::geometry()
{
auto &instance = WindowController::instance();
QWindow window;
window.setGeometry(0, 0, 200, 200);
instance.setWindow(&window);
QCOMPARE(instance.window(), &window);
instance.saveGeometry();
const auto stateConfig = KSharedConfig::openStateConfig();
KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
QCOMPARE(KWindowConfig::hasSavedWindowSize(windowGroup), true);
window.setGeometry(0, 0, 400, 400);
QCOMPARE(window.geometry(), QRect(0, 0, 400, 400));
instance.restoreGeometry();
QCOMPARE(window.geometry(), QRect(0, 0, 200, 200));
}
void WindowControllerTest::showAndRaise() void WindowControllerTest::showAndRaise()
{ {
auto &instance = WindowController::instance(); auto &instance = WindowController::instance();

View File

@@ -1,37 +0,0 @@
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
qt_add_executable(timeline-memtest
main.cpp
)
target_link_libraries(timeline-memtest PRIVATE neochatplugin Timelineplugin)
target_link_libraries(timeline-memtest PUBLIC
Qt::Core
Qt::Quick
Qt::Qml
Qt::Gui
Qt::QuickControls2
Qt::Widgets
KF6::I18nQml
KF6::Kirigami
QuotientQt6
LibNeoChat
Timeline
)
ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline-memtest
QML_FILES
Main.qml
SOURCES
memtesttimelinemodel.cpp
memtesttimelinemodel.h
DEPENDENCIES
QtCore
QtQuick
IMPORTS
org.kde.neochat.timeline
)

View File

@@ -1,34 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.neochat
QQC2.ApplicationWindow {
id: root
title: "Timeline Memory Test"
minimumWidth: Kirigami.Units.gridUnit * 30
minimumHeight: Kirigami.Units.gridUnit * 30
visible: true
QQC2.ScrollView {
width: root.width
height: root.height
contentItem: ListView {
cacheBuffer: 1000000
model: messageFilterModel
delegate: EventDelegate {
room: memTestTimelineModel.room
}
}
}
}

View File

@@ -1,34 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <KLocalizedQmlContext>
#include <KLocalizedString>
#include "memtesttimelinemodel.h"
#include "models/messagefiltermodel.h"
using namespace Qt::StringLiterals;
int main(int argc, char **argv)
{
QApplication app(argc, argv);
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
QQmlApplicationEngine engine;
KLocalization::setupLocalizedContext(&engine);
MemTestTimelineModel *memTestTimelineModel = new MemTestTimelineModel;
MessageFilterModel *messageFilterModel = new MessageFilterModel(nullptr, memTestTimelineModel);
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
return app.exec();
}

View File

@@ -1,379 +0,0 @@
{
"ephemeral": {
"events": [
{
"content": {
"$1000000000000:example.org": {
"m.read": {
"@alice:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000000:example.org": {
"m.read": {
"@bob:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@tim:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@example:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@jeff:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@tina:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@sally:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@fred:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Example",
"membership": "join"
},
"event_id": "$143273582555PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "alice:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Bob",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "bob:example.org",
"state_key": "@bob:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Tim",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "tim:example.org",
"state_key": "@tim:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Jeff",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "jeff:example.org",
"state_key": "@jeff:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Tina",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "tina:example.org",
"state_key": "@tina:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Sally",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "sally:example.org",
"state_key": "@sally:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Fred",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "fred:example.org",
"state_key": "@fred:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example text message",
"format": "org.matrix.custom.html",
"formatted_body": "This is an example<br>text message",
"msgtype": "m.text"
},
"event_id": 0,
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "This is a highlight @bob:example.org",
"msgtype": "m.text"
},
"event_id": 1,
"origin_server_ts": 1000000000001,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": 1,
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1000000000002,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:example.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": 2,
"age": 390159120
},
{
"content": {
"body": "reply",
"format": "org.matrix.custom.html",
"formatted_body": "reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": 0
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1000000000003,
"sender": "@alice:example.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": 3,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": 4,
"origin_server_ts": 1000000000004,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
},
{
"content": {
"body": "```cpp\nint main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(\"neochat\"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(\"org.kde.neochat.timeline-memtest\", \"Main\");\n\n return app.exec();\n}\n```",
"format": "org.matrix.custom.html",
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(&quot;neochat&quot;));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(&quot;org.kde.neochat.timeline-memtest&quot;, &quot;Main&quot;);\n\n return app.exec();\n}\n</code></pre>",
"msgtype": "m.text"
},
"event_id": 5,
"origin_server_ts": 1000000000005,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"msgtype": "m.text"
},
"event_id": 6,
"origin_server_ts": 1000000000006,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"format": "org.matrix.custom.html",
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
"msgtype": "m.text"
},
"event_id": 7,
"origin_server_ts": 1000000000007,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -1,36 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "memtesttimelinemodel.h"
#include <Quotient/events/eventcontent.h>
#include <Quotient/events/roommessageevent.h>
using namespace Quotient;
MemTestTimelineModel::MemTestTimelineModel(QObject *parent)
: MessageModel(parent)
{
beginResetModel();
m_connection = Connection::makeMockConnection(u"@bob:example.org"_s);
m_room = new MemTestRoom(m_connection, u"#memtestroom:example.org"_s, u"memtest-sync.json"_s);
for (const auto &eventIt : m_room->messageEvents()) {
Q_EMIT newEventAdded(eventIt.event());
}
endResetModel();
}
std::optional<std::reference_wrapper<const RoomEvent>> MemTestTimelineModel::getEventForIndex(QModelIndex index) const
{
return *m_room->messageEvents().at(index.row()).event();
}
int MemTestTimelineModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_room->messageEvents().size();
}
#include "moc_memtesttimelinemodel.cpp"

View File

@@ -1,117 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/events/roomevent.h>
#include <Quotient/syncdata.h>
#include "models/messagemodel.h"
namespace Quotient
{
class Connection;
}
class NeoChatRoom;
class MemTestRoom : public NeoChatRoom
{
public:
MemTestRoom(Quotient::Connection *connection, const QString &roomName, const QString &syncFileName = {})
: NeoChatRoom(connection, roomName, Quotient::JoinState::Join)
{
syncNewEvents(syncFileName);
}
void update(Quotient::SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
void syncNewEvents(const QString &syncFileName)
{
if (!syncFileName.isEmpty()) {
QFile testSyncFile;
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
testSyncFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
auto timelineJson = testSyncJson["timeline"_L1].toObject();
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);
testSyncJson["timeline"_L1] = timelineJson;
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson);
update(std::move(roomData));
}
}
QJsonArray multiplyEvents(QJsonArray events, int factor)
{
QJsonArray newArray;
int eventNum = 0;
int ts = 0;
for (int i = 0; i < factor; ++i) {
for (const auto &event : events) {
auto eventObject = event.toObject();
auto contentJson = eventObject["content"_L1].toObject();
if (contentJson.contains("m.relates_to"_L1)) {
auto relatesToJson = contentJson["m.relates_to"_L1].toObject();
if (relatesToJson.contains("m.in_reply_to"_L1)) {
auto replyJson = relatesToJson["m.in_reply_to"_L1].toObject();
const auto currentId = eventObject["event_id"_L1].toInt();
const auto currentReplyId = replyJson["event_id"_L1].toInt();
replyJson["event_id"_L1] = "$%1:example.org"_L1.arg(QString::number(eventNum - (currentId - currentReplyId)));
relatesToJson["m.in_reply_to"_L1] = replyJson;
} else if (relatesToJson.contains("event_id"_L1)) {
const auto currentId = eventObject["event_id"_L1].toInt();
const auto currentRelationId = relatesToJson["event_id"_L1].toInt();
relatesToJson["event_id"_L1] = "$%1:example.org"_L1.arg(QString::number(eventNum - (currentId - currentRelationId)));
}
contentJson["m.relates_to"_L1] = relatesToJson;
eventObject["content"_L1] = contentJson;
}
eventObject["event_id"_L1] = "$%1:example.org"_L1.arg(QString::number(eventNum));
eventObject["origin_server_ts"_L1] = ts;
auto unsignedJson = eventObject["unsigned"_L1].toObject();
unsignedJson["age"_L1] = ts;
eventObject["unsigned"_L1] = unsignedJson;
newArray.append(eventObject);
++eventNum;
++ts;
}
}
return newArray;
}
};
/**
* @class MemTestTimelineModel
*
* This is a special version of the MessageModel design to load an unchanging set
* of events from a json file so that timeline memory optimisations can be measured.
*/
class MemTestTimelineModel : public MessageModel
{
Q_OBJECT
QML_ELEMENT
public:
explicit MemTestTimelineModel(QObject *parent = nullptr);
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
private:
QPointer<Quotient::Connection> m_connection;
std::vector<Quotient::RoomEventPtr> m_events;
std::optional<std::reference_wrapper<const Quotient::RoomEvent>> getEventForIndex(QModelIndex index) const override;
};

View File

@@ -22,11 +22,11 @@
<name xml:lang="eo">NeoChat</name> <name xml:lang="eo">NeoChat</name>
<name xml:lang="es">NeoChat</name> <name xml:lang="es">NeoChat</name>
<name xml:lang="eu">NeoChat</name> <name xml:lang="eu">NeoChat</name>
<name xml:lang="fa">نئوچت</name>
<name xml:lang="fi">NeoChat</name> <name xml:lang="fi">NeoChat</name>
<name xml:lang="fr">NeoChat</name> <name xml:lang="fr">NeoChat</name>
<name xml:lang="gl">NeoChat</name> <name xml:lang="gl">NeoChat</name>
<name xml:lang="he">NeoChat</name> <name xml:lang="he">NeoChat</name>
<name xml:lang="hi">नियोचैट</name>
<name xml:lang="hu">NeoChat</name> <name xml:lang="hu">NeoChat</name>
<name xml:lang="ia">Neochat</name> <name xml:lang="ia">Neochat</name>
<name xml:lang="id">NeoChat</name> <name xml:lang="id">NeoChat</name>
@@ -41,57 +41,54 @@
<name xml:lang="pl">NeoChat</name> <name xml:lang="pl">NeoChat</name>
<name xml:lang="pt">NeoChat</name> <name xml:lang="pt">NeoChat</name>
<name xml:lang="pt-BR">NeoChat</name> <name xml:lang="pt-BR">NeoChat</name>
<name xml:lang="ro">NeoChat</name>
<name xml:lang="ru">NeoChat</name> <name xml:lang="ru">NeoChat</name>
<name xml:lang="sa">नवचैट्</name>
<name xml:lang="sk">NeoChat</name> <name xml:lang="sk">NeoChat</name>
<name xml:lang="sl">NeoChat</name> <name xml:lang="sl">NeoChat</name>
<name xml:lang="sv">NeoChat</name> <name xml:lang="sv">NeoChat</name>
<name xml:lang="ta">நியோச்சாட்</name> <name xml:lang="ta">நியோச்சாட்</name>
<name xml:lang="tr">NeoChat</name> <name xml:lang="tr">NeoChat</name>
<name xml:lang="uk">NeoChat</name> <name xml:lang="uk">NeoChat</name>
<name xml:lang="x-test">xxNeoChatxx</name>
<name xml:lang="zh-CN">NeoChat</name> <name xml:lang="zh-CN">NeoChat</name>
<name xml:lang="zh-TW">NeoChat</name> <name xml:lang="zh-TW">NeoChat</name>
<summary>Chat on Matrix</summary> <summary>Chat with your friends on matrix</summary>
<summary xml:lang="ar">دردش على ماتركس</summary> <summary xml:lang="ar">دردش مع أصدقائك على ماتركس</summary>
<summary xml:lang="ca">Xat a Matrix</summary> <summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
<summary xml:lang="ca-valencia">Xat a Matrix</summary> <summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary>
<summary xml:lang="de">Über Matrix unterhalten</summary> <summary xml:lang="cs">Mluvte se svými přáteli na Matrixu</summary>
<summary xml:lang="en-GB">Chat on Matrix</summary> <summary xml:lang="de">Mit den Freunden auf Matrix unterhalten</summary>
<summary xml:lang="eo">Babilo en Matrix</summary> <summary xml:lang="en-GB">Chat with your friends on matrix</summary>
<summary xml:lang="es">Charle en Matrix</summary> <summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
<summary xml:lang="eu">Berriketa Matrix-en</summary> <summary xml:lang="es">Charle con sus amigos en matrix</summary>
<summary xml:lang="fi">Keskustelu Matrixissä</summary> <summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
<summary xml:lang="fr">Discuter sur Matrix</summary> <summary xml:lang="fi">Keskustelu ystäviesi kanssa Matrixissa</summary>
<summary xml:lang="gl">Charlar en Matrix</summary> <summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
<summary xml:lang="he">התכתבות דרך Matrix</summary> <summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
<summary xml:lang="hi">मैट्रिक्स पर चैट करें</summary> <summary xml:lang="he">התכתבות עם החברים שלך ב־matrix</summary>
<summary xml:lang="hu">Csevegés Matrixon</summary> <summary xml:lang="hu">Csevegjen barátaival a matrixon</summary>
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary> <summary xml:lang="ia">Starta Conversation con tu amicos sur matrix</summary>
<summary xml:lang="it">Chat su Matrix</summary> <summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
<summary xml:lang="ka">საუბრეთ Matrix-ზე</summary> <summary xml:lang="ka">საუბრეთ მეგობრებს Matrix-ზე</summary>
<summary xml:lang="ko">Matrix에서 대화하기</summary> <summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
<summary xml:lang="lv">Tērzējiet „Matrix“ tīklā</summary> <summary xml:lang="lv">Tērzējiet ar saviem draugiem „Matrix“ tīklā</summary>
<summary xml:lang="nl">Chat op Matrix</summary> <summary xml:lang="nl">Met uw vrienden chatten op matrix</summary>
<summary xml:lang="nn">Prat med via Matrix</summary> <summary xml:lang="nn">Prat med vennar på Matrix</summary>
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary> <summary xml:lang="pl">Rozmawiaj ze swoimi znajomymi w Matriksie</summary>
<summary xml:lang="pt-BR">Bate-papo na Matrix</summary> <summary xml:lang="ru">Общение с друзьями в Matrix</summary>
<summary xml:lang="ro">Discutați pe Matrix</summary> <summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
<summary xml:lang="ru">Общение в Matrix</summary> <summary xml:lang="sv">Chatta med dina vänner på Matrix</summary>
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary> <summary xml:lang="ta">மேட்ரிக்ஸு மூலம் உங்கள் நண்பர்களிடம் பேசலாம்</summary>
<summary xml:lang="sl">Klepet na Matrixu</summary> <summary xml:lang="tr">Matrixte arkadaşlarınızla sohbet edin</summary>
<summary xml:lang="sv">Chatta på Matrix</summary> <summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary> <summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary> <summary xml:lang="zh-CN">Matrix 上与朋友聊天</summary>
<summary xml:lang="uk">Спілкування у Matrix</summary> <summary xml:lang="zh-TW">在 Matrix 上與您的朋友聊天</summary>
<summary xml:lang="zh-TW">在 Matrix 上聊天</summary>
<description> <description>
<p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p> <p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
<p xml:lang="ar">نيوتشات هو تطبيق دردشة يتيح لك الاستفادة الكاملة من شبكة Matrix. فهو يوفر لك طريقة آمنة لإرسال الرسائل النصية ومقاطع الفيديو والملفات الصوتية إلى عائلتك وزملائك وأصدقائك.</p> <p xml:lang="ar">نيوتشات هو تطبيق دردشة يتيح لك الاستفادة الكاملة من شبكة Matrix. فهو يوفر لك طريقة آمنة لإرسال الرسائل النصية ومقاطع الفيديو والملفات الصوتية إلى عائلتك وزملائك وأصدقائك.</p>
<p xml:lang="ca">El NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p> <p xml:lang="ca">El NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
<p xml:lang="ca-valencia">NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p> <p xml:lang="ca-valencia">NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
<p xml:lang="de">NeoChat ist eine Anwendung für Unterhaltungen mit allen Vorteilen des Matrix-Netzwerkes. Sie bietet eine sichere Möglichkeit zum Versenden von Nachrichten, Videos und Audiodateien and die Familienmitglieder.</p> <p xml:lang="de">NeoChat ist eine Anwendung für Unterhaltungen mit allen Vorteilen des Matrix-Netzwerkes. Sie bietet eine sichere Möglichkeit zum Versenden von Nachrichten, Videos und Audiodateien and die Familienmitglieder.</p>
<p xml:lang="el">Το NeoChat είναι μια εφαρμογή συνομιλίας που σας επιτρέπει να εκμεταλλευτείτε πλήρως το δίκτυο Matrix. Σας παρέχει έναν ασφαλή τρόπο να στέλνετε μηνύματα κειμένου, βίντεο και αρχεία ήχου στην οικογένεια, τους συναδέλφους και τους φίλους σας.</p>
<p xml:lang="en-GB">NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p> <p xml:lang="en-GB">NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
<p xml:lang="eo">NeoChat estas babilej-apo, kiu ebligas al vi plene profiti de la Matrix-reto. Ĝi provizas al vi sekuran manieron sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj.</p> <p xml:lang="eo">NeoChat estas babilej-apo, kiu ebligas al vi plene profiti de la Matrix-reto. Ĝi provizas al vi sekuran manieron sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj.</p>
<p xml:lang="es">NeoChat es una aplicación de chat que le permite aprovechar al máximo la red Matrix. Le proporciona un modo seguro de enviar mensajes de texto, vídeos y archivos de sonido a su familia, colegas y amigos.</p> <p xml:lang="es">NeoChat es una aplicación de chat que le permite aprovechar al máximo la red Matrix. Le proporciona un modo seguro de enviar mensajes de texto, vídeos y archivos de sonido a su familia, colegas y amigos.</p>
@@ -100,40 +97,34 @@
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p> <p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p>
<p xml:lang="gl">NeoChat é unha aplicación de conversa que lle permite usar todas as funcionalidades da rede Matrix. Fornece unha forma segura de enviar mensaxes de texto e ficheiros de vídeo e son a familiares, amizades ou no traballo.</p> <p xml:lang="gl">NeoChat é unha aplicación de conversa que lle permite usar todas as funcionalidades da rede Matrix. Fornece unha forma segura de enviar mensaxes de texto e ficheiros de vídeo e son a familiares, amizades ou no traballo.</p>
<p xml:lang="he">NeoChat הוא יישום התכתבות שמאפשר לך לנצל את רשת Matrix במלואה. הוא מספק דרך מאובטחת לשליחת הודעות כתובות, סרטונים וקובצי שמע למשפחה, לעמיתים לעבודה ולחברים.</p> <p xml:lang="he">NeoChat הוא יישום התכתבות שמאפשר לך לנצל את רשת Matrix במלואה. הוא מספק דרך מאובטחת לשליחת הודעות כתובות, סרטונים וקובצי שמע למשפחה, לעמיתים לעבודה ולחברים.</p>
<p xml:lang="hi">नियोचैट एक चैट ऐप है जो आपको मैट्रिक्स नेटवर्क का पूरा लाभ उठाने देता है। यह आपको अपने परिवार, सहकर्मियों और दोस्तों को टेक्स्ट संदेश, वीडियो और ऑडियो फ़ाइलें भेजने का एक सुरक्षित तरीका प्रदान करता है।</p>
<p xml:lang="hu">A NeoChat egy olyan csevegőalkalmazás, amellyel teljes mértékben kihasználhatja a Matrix hálózatot. Biztonságos módot biztosít szöveges üzenetek, videók és hangfájlok küldéséhez családtagjainak, kollégáinak és barátainak.</p> <p xml:lang="hu">A NeoChat egy olyan csevegőalkalmazás, amellyel teljes mértékben kihasználhatja a Matrix hálózatot. Biztonságos módot biztosít szöveges üzenetek, videók és hangfájlok küldéséhez családtagjainak, kollégáinak és barátainak.</p>
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p> <p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
<p xml:lang="it">NeoChat è un'applicazione di chat che ti consente di sfruttare appieno la rete Matrix. Ti fornisce un modo sicuro per inviare messaggi di testo, video e file audio a familiari, colleghi e amici.</p> <p xml:lang="it">NeoChat è un'applicazione di chat che ti consente di sfruttare appieno la rete Matrix. Ti fornisce un modo sicuro per inviare messaggi di testo, video e file audio a familiari, colleghi e amici.</p>
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p> <p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
<p xml:lang="ko">NeoChat은 Matrix 네트워크를 사용하는 채팅 앱입니다. 텍스트 메시지, 동영상, 오디오 파일을 가족, 친구, 동료와 안전하게 공유할 수 있습니다.</p>
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p> <p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p> <p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p> <p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p> <p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
<p xml:lang="pt-BR">O NeoChat é um aplicativo de bate-papo que permite que você aproveite ao máximo a rede Matrix. Ele oferece uma maneira segura de enviar mensagens de texto, vídeos e arquivos de áudio para sua família, colegas e amigos.</p>
<p xml:lang="ro">NeoChat e o aplicație de discuții ce vă ajută să profitați din plin de rețeaua Matrix. Aceasta oferă o modalitate sigură de a trimite mesaje textuale, videoclipuri și fișiere audio familiei, colegilor și prietenilor.</p>
<p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p> <p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p>
<p xml:lang="sa">NeoChat इति एकं गपशप-अनुप्रयोगं यत् भवान् Matrix-जालस्य पूर्णं लाभं ग्रहीतुं शक्नोति । एतत् भवन्तं भवतः परिवाराय, सहकारिभ्यः, मित्रेभ्यः च पाठसन्देशान्, भिडियो, श्रव्यसञ्चिकाः च प्रेषयितुं सुरक्षितं मार्गं प्रदाति ।</p>
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p> <p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p> <p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
<p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p> <p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p>
<p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p> <p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p>
<p xml:lang="x-test">xxNeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.xx</p>
<p xml:lang="zh-TW">NeoChat 是一個讓您能夠完全利用 Matrix 網路的聊天應用程式。它讓您安全地傳送文字訊息、影片或音訊檔給家人、同事或朋友等等。</p> <p xml:lang="zh-TW">NeoChat 是一個讓您能夠完全利用 Matrix 網路的聊天應用程式。它讓您安全地傳送文字訊息、影片或音訊檔給家人、同事或朋友等等。</p>
<p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p> <p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. يوفر نيوتشات كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP و تعدد الخيوط وبعض جوانب التعمية من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار، ولكن يبقى الهدف توفير تطبيق للمواصفات بأكملها.</p> <p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p> <p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p> <p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
<p xml:lang="de">NeoChat versucht eine vollumfängliche Anwendung für die Spezifikation von Matrix zu sein. Damit wird alles der aktuellen stabilen Spezifikation mit den erwähnenswerten Ausnahmen von VoIP, Diskussionsfäden und ein paar Teilen der Ende-zu-Ende-Verschlüsselung unterstützt. Zudem sind andere kleinere Auslassungen vorhanden, da sich die Matrixspezifikation ständig weiterentwickelt. Nichtsdestotrotz soll letztendlich die gesamte Spezifikation unterstützt werden.</p> <p xml:lang="de">NeoChat versucht eine vollumfängliche Anwendung für die Spezifikation von Matrix zu sein. Damit wird alles der aktuellen stabilen Spezifikation mit den erwähnenswerten Ausnahmen von VoIP, Diskussionsfäden und ein paar Teilen der Ende-zu-Ende-Verschlüsselung unterstützt. Zudem sind andere kleinere Auslassungen vorhanden, da sich die Matrixspezifikation ständig weiterentwickelt. Nichtsdestotrotz soll letztendlich die gesamte Spezifikation unterstützt werden.</p>
<p xml:lang="el">Το NeoChat στοχεύει να είναι μια πλήρως εξοπλισμένη εφαρμογή για τις προδιαγραφές Matrix. Ως εκ τούτου, υποστηρίζονται όλα τα στοιχεία της τρέχουσας σταθερής προδιαγραφής με τις αξιοσημείωτες εξαιρέσεις του VoIP, των νημάτων και ορισμένων πτυχών της κρυπτογράφησης στα άκρα. Υπάρχουν μερικές άλλες μικρότερες παραλείψεις που οφείλονται στο γεγονός ότι η προδιαγραφή Matrix εξελίσσεται συνεχώς, αλλά ο στόχος παραμένει να παρέχεται τελικά υποστήριξη για ολόκληρη την προδιαγραφή.</p>
<p xml:lang="en-GB">NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p> <p xml:lang="en-GB">NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
<p xml:lang="eo">NeoChat celas esti plene kapabla aplikaĵo por la Matrix-specifo. Kiel tia, ĉio en la nuna stabila specifo kun la rimarkindaj esceptoj de VoIP, fadenoj kaj kelkaj aspektoj de Fin-al-Fina Ĉifrado estas subtenataj. Estas kelkaj aliaj pli malgrandaj preterlasoj pro la fakto, ke la Matrix-speco konstante evoluas, sed la celo restas provizi finfine subtenon por la tuta specifaĵo.</p> <p xml:lang="eo">NeoChat celas esti plene kapabla aplikaĵo por la Matrix-specifo. Kiel tia, ĉio en la nuna stabila specifo kun la rimarkindaj esceptoj de VoIP, fadenoj kaj kelkaj aspektoj de Fin-al-Fina Ĉifrado estas subtenataj. Estas kelkaj aliaj pli malgrandaj preterlasoj pro la fakto, ke la Matrix-speco konstante evoluas, sed la celo restas provizi finfine subtenon por la tuta specifaĵo.</p>
<p xml:lang="es">NeoChat pretende ser una aplicación con todas las funciones para la especificación de Matrix. Como tal, admite todo en la especificación estable actual, con las notables excepciones de VoIP, subprocesos y algunas funciones de cifrado de extremo a extremo. Existen algunas omisiones menos importantes debido al hecho de que la especificación de Matrix está en constante evolución, pero el objetivo sigue siendo brindar compatibilidad final con toda la especificación.</p> <p xml:lang="es">NeoChat pretende ser una aplicación con todas las funciones para la especificación de Matrix. Como tal, admite todo en la especificación estable actual, con las notables excepciones de VoIP, subprocesos y algunas funciones de cifrado de extremo a extremo. Existen algunas omisiones menos importantes debido al hecho de que la especificación de Matrix está en constante evolución, pero el objetivo sigue siendo brindar compatibilidad final con toda la especificación.</p>
<p xml:lang="eu">«NeoChat»ek «Matrix» zehaztapenaren ezaugarri guztiak eskaintzen dituen aplikazio bat izan nahi du. Beraz, egungo zehaztapen egonkorrean dagoen guztiaren euskarria du, VoIP, hariak eta muturren artean zifratzeko salbuespen nabarmenekin. Badira beste ez-betetze txikiago batzuk, «Matrix»en zehaztapena etengabe eboluzioan dagoelako, baina azken helburua zehaztapen osoaren euskarria ematea izaten jarraitzen du.</p> <p xml:lang="eu">«NeoChat»ek «Matrix» zehaztapenaren ezaugarri guztiak eskaintzen dituen aplikazio bat izan nahi du. Beraz, egungo zehaztapen egonkorrean dagoen guztiaren euskarria du, VoIP, hariak eta muturren artean zifratzeko salbuespen nabarmenekin. Badira beste ez-betetze txikiago batzuk, «Matrix»en zehaztapena etengabe eboluzioan dagoelako, baina azken helburua zehaztapen osoaren euskarria ematea izaten jarraitzen du.</p>
<p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p> <p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p>
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p> <p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é fornecer compatibilidade coa especificación completa.</p> <p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
<p xml:lang="he">NeoChat מתיימר להיות יישום עתיר יכולות לפי מפרט Matrix. כיוון שזה ייעודו, כל מה שבמפרט היציב עם חריגות משמעותיות כגון VoIP, שרשורים ועוד מגוון היבטים של הצפנה מקצה לקצה נתמכים גם הם. יש מספר השמטות קטן עקב העובדה שהמפרט של Matrix ממשיך להתפתח אך המטרה היא להמשיך לספק תמיכה בסופו של דבר לכל המפרט.</p> <p xml:lang="he">NeoChat מתיימר להיות יישום עתיר יכולות לפי מפרט Matrix. כיוון שזה ייעודו, כל מה שבמפרט היציב עם חריגות משמעותיות כגון VoIP, שרשורים ועוד מגוון היבטים של הצפנה מקצה לקצה נתמכים גם הם. יש מספר השמטות קטן עקב העובדה שהמפרט של Matrix ממשיך להתפתח אך המטרה היא להמשיך לספק תמיכה בסופו של דבר לכל המפרט.</p>
<p xml:lang="hi">नियोचैट का लक्ष्य मैट्रिक्स विनिर्देश के लिए एक पूर्ण विशेषताओं वाला अनुप्रयोग बनना है। इस प्रकार वर्तमान स्थिर विनिर्देश में वीओआईपी, थ्रेड्स और एंड-टू-एंड एन्क्रिप्शन के कुछ पहलुओं के उल्लेखनीय अपवादों के साथ सब कुछ समर्थित है। मैट्रिक्स विनिर्देश लगातार विकसित हो रहा है, इस तथ्य के कारण कुछ अन्य छोटी चूकें हैं, लेकिन उद्देश्य पूरे विनिर्देश के लिए अंतिम समर्थन प्रदान करना है।</p>
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p> <p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p>
<p xml:lang="ia">NeoChat aspira a esser un application plenmente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p> <p xml:lang="ia">NeoChat aspira a esser un application plenmente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p> <p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
@@ -144,21 +135,18 @@
<p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p> <p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p>
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p> <p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p> <p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
<p xml:lang="pt-BR">O NeoChat pretende ser um aplicativo completo para a especificação Matrix. Dessa forma, tudo na especificação estável atual, com as notáveis exceções de VoIP, tópicos e alguns aspectos da criptografia de ponta a ponta, é suportado. Há algumas outras pequenas omissões devido ao fato de a especificação Matrix estar em constante evolução, mas o objetivo continua sendo fornecer suporte eventual para toda a especificação.</p>
<p xml:lang="ro">NeoChat vrea să fie o aplicație completă pentru specificațiile Matrix. Astfel, susține tot ce se găsește acum în specificațiile stabile cu excepția VoIP, a firelor de discuții, și a unor părți din criptarea punct-la-punct. Sunt și câteva omisiuni minore din cauza faptului că specificația Matrix evoluează continuu, dar scopul rămâne acela de a implementa întreaga specificație.</p>
<p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p> <p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p>
<p xml:lang="sa">NeoChat इत्यस्य उद्देश्यं Matrix विनिर्देशस्य कृते पूर्णतया विशेषतायुक्तः अनुप्रयोगः भवितुम् अस्ति । यथा तथा वर्तमानस्थिरविनिर्देशे सर्वं VoIP इत्यस्य उल्लेखनीयअपवादैः सह, थ्रेड्स तथा च End-to-End Encryption इत्यस्य केचन पक्षाः समर्थिताः सन्ति । अन्ये कतिचन लघु लोपाः सन्ति यतोहि Matrix spec निरन्तरं विकसितः अस्ति परन्तु उद्देश्यं सम्पूर्ण spec कृते अन्ततः समर्थनं प्रदातुं अवशिष्टम् अस्ति</p>
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p> <p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
<p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p> <p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifrelemenin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p> <p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifrelemenin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p> <p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.xx</p>
<p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p> <p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p>
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p> <p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يوفر نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p> <p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p> <p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
<p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p> <p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
<p xml:lang="de">Durch die Weiterentwicklung der Matrix-Spezifikation unterstützt auch NeoChat einige als noch instabil gekennzeichnete Funktionen. Derzeit sind das:</p> <p xml:lang="de">Durch die Weiterentwicklung der Matrix-Spezifikation unterstützt auch NeoChat einige als noch instabil gekennzeichnete Funktionen. Derzeit sind das:</p>
<p xml:lang="el">Λόγω της φύσης της ανάπτυξης των προδιαγραφών Matrix, το NeoChat υποστηρίζει επίσης πολλά ασταθή χαρακτηριστικά. Επί του παρόντος, αυτά είναι:</p>
<p xml:lang="en-GB">Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p> <p xml:lang="en-GB">Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
<p xml:lang="eo">Pro la naturo de la Matrix-specifevoluo NeoChat ankaŭ subtenas multajn malstabilajn funkciojn. Nuntempe ĉi tiuj estas:</p> <p xml:lang="eo">Pro la naturo de la Matrix-specifevoluo NeoChat ankaŭ subtenas multajn malstabilajn funkciojn. Nuntempe ĉi tiuj estas:</p>
<p xml:lang="es">Debido a la naturaleza del desarrollo de la especificación de Matrix, NeoChat también permite numerosas funciones no estables, como:</p> <p xml:lang="es">Debido a la naturaleza del desarrollo de la especificación de Matrix, NeoChat también permite numerosas funciones no estables, como:</p>
@@ -167,7 +155,6 @@
<p xml:lang="fr">En raison de la nature du développement des spécifications du protocole Matrix, NeoChat prend également en charge de nombreuses fonctionnalités instables. Actuellement, ce sont :</p> <p xml:lang="fr">En raison de la nature du développement des spécifications du protocole Matrix, NeoChat prend également en charge de nombreuses fonctionnalités instables. Actuellement, ce sont :</p>
<p xml:lang="gl">Debido á natureza do desenvolvemento da especificación de Matrix, NeoChat tamén inclúe varias funcionalidades non estábeis:</p> <p xml:lang="gl">Debido á natureza do desenvolvemento da especificación de Matrix, NeoChat tamén inclúe varias funcionalidades non estábeis:</p>
<p xml:lang="he">מטבע הדברים, הפיתוח של NeoChat תומך במגוון יכולות מפוקפקות כתלות בהתפתחות המפרט הטכני של Matrix. היכולות האלה הן:</p> <p xml:lang="he">מטבע הדברים, הפיתוח של NeoChat תומך במגוון יכולות מפוקפקות כתלות בהתפתחות המפרט הטכני של Matrix. היכולות האלה הן:</p>
<p xml:lang="hi">मैट्रिक्स विनिर्देश विकास की प्रकृति के कारण नियोचैट कई अस्थिर सुविधाओं का भी समर्थन करता है। वर्तमान में ये हैं:</p>
<p xml:lang="hu">A Matrix specifikáció fejlesztésének jellegéből adódóan a NeoChat számos instabil funkciót is támogat. Jelenleg a következőket:</p> <p xml:lang="hu">A Matrix specifikáció fejlesztésének jellegéből adódóan a NeoChat számos instabil funkciót is támogat. Jelenleg a következőket:</p>
<p xml:lang="ia">Debite al natura del disveloppamento de specification de Matrix NeoChat tamben supporta numerose characteristicas instabile. Currentemente istes es:</p> <p xml:lang="ia">Debite al natura del disveloppamento de specification de Matrix NeoChat tamben supporta numerose characteristicas instabile. Currentemente istes es:</p>
<p xml:lang="it">A causa della natura dello sviluppo delle specifiche Matrix, NeoChat supporta anche numerose funzionalità instabili. Attualmente queste sono:</p> <p xml:lang="it">A causa della natura dello sviluppo delle specifiche Matrix, NeoChat supporta anche numerose funzionalità instabili. Attualmente queste sono:</p>
@@ -178,22 +165,19 @@
<p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p> <p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p>
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p> <p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p> <p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
<p xml:lang="pt-BR">Devido à natureza do desenvolvimento da especificação Matrix, o NeoChat também suporta diversos recursos instáveis. Atualmente, são eles:</p>
<p xml:lang="ro">Datorită modului de dezvoltare a specificațiilor Matrix, NeoChat susține și numeroase caracteristici nestabile. Acum, acestea sunt:</p>
<p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p> <p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p>
<p xml:lang="sa">Matrix विनिर्देशविकासस्य प्रकृतेः कारणात् NeoChat अपि अनेकानाम् अस्थिरविशेषतानां समर्थनं करोति । सम्प्रति एते सन्ति :</p>
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p> <p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
<p xml:lang="sv">På grund av sättet Matrix-specifikationens utvecklas, stöder NeoChat också ett stor antal instabila funktioner. För närvarande är de:</p> <p xml:lang="sv">På grund av sättet Matrix-specifikationens utvecklas, stöder NeoChat också ett stor antal instabila funktioner. För närvarande är de:</p>
<p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் வித‍த்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p> <p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் வித‍த்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p> <p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p> <p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
<p xml:lang="zh-TW">由於 Matrix 標準的開發流程的緣故NeoChat 也支援數個非穩定版的功能。目前這些功能是:</p> <p xml:lang="zh-TW">由於 Matrix 標準的開發流程的緣故NeoChat 也支援數個非穩定版的功能。目前這些功能是:</p>
<ul> <ul>
<li>Polls - MSC3381</li> <li>Polls - MSC3381</li>
<li xml:lang="ar">التصويت - MSC3381</li> <li xml:lang="ar">التصويت - MSC3381</li>
<li xml:lang="ca">Votacions - MSC3381</li> <li xml:lang="ca">Enquestes - MSC3381</li>
<li xml:lang="ca-valencia">Votacions - MSC3381</li> <li xml:lang="ca-valencia">Enquestes - MSC3381</li>
<li xml:lang="el">Δημοσκοπήσεις - MSC3381</li>
<li xml:lang="en-GB">Polls - MSC3381</li> <li xml:lang="en-GB">Polls - MSC3381</li>
<li xml:lang="eo">Enketoj - MSC3381</li> <li xml:lang="eo">Enketoj - MSC3381</li>
<li xml:lang="es">Encuestas - MSC3381</li> <li xml:lang="es">Encuestas - MSC3381</li>
@@ -202,7 +186,6 @@
<li xml:lang="fr">Sondages - MSC3381</li> <li xml:lang="fr">Sondages - MSC3381</li>
<li xml:lang="gl">Enquisas — MSC3381</li> <li xml:lang="gl">Enquisas — MSC3381</li>
<li xml:lang="he">סקרים - MSC3381</li> <li xml:lang="he">סקרים - MSC3381</li>
<li xml:lang="hi">पोल - MSC3381</li>
<li xml:lang="hu">Szavazások - MSC3381</li> <li xml:lang="hu">Szavazások - MSC3381</li>
<li xml:lang="ia">Inquestas - MSC3381</li> <li xml:lang="ia">Inquestas - MSC3381</li>
<li xml:lang="it">Sondaggi - MSC3381</li> <li xml:lang="it">Sondaggi - MSC3381</li>
@@ -213,21 +196,18 @@
<li xml:lang="nn">Avstemmingar  MSC3381</li> <li xml:lang="nn">Avstemmingar  MSC3381</li>
<li xml:lang="pl">Ankiety - MSC3381</li> <li xml:lang="pl">Ankiety - MSC3381</li>
<li xml:lang="pt">Inquéritos - MSC3381</li> <li xml:lang="pt">Inquéritos - MSC3381</li>
<li xml:lang="pt-BR">Enquetes - MSC3381</li>
<li xml:lang="ro">Sondaje - MSC3381</li>
<li xml:lang="ru">Голосования — MSC3381</li> <li xml:lang="ru">Голосования — MSC3381</li>
<li xml:lang="sa">मतदान - MSC3381</li>
<li xml:lang="sl">Polls - MSC3381</li> <li xml:lang="sl">Polls - MSC3381</li>
<li xml:lang="sv">Polls - MSC3381</li> <li xml:lang="sv">Polls - MSC3381</li>
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li> <li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
<li xml:lang="tr">Anketler — MSC3381</li> <li xml:lang="tr">Anketler — MSC3381</li>
<li xml:lang="uk">Опитування - MSC3381</li> <li xml:lang="uk">Опитування - MSC3381</li>
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
<li xml:lang="zh-TW">投票 - MSC3381</li> <li xml:lang="zh-TW">投票 - MSC3381</li>
<li>Sticker Packs - MSC2545</li> <li>Sticker Packs - MSC2545</li>
<li xml:lang="ar">حزم الملصقات - MSC2545</li> <li xml:lang="ar">حزم الملصقات - MSC2545</li>
<li xml:lang="ca">Paquets d'adhesius - MSC2545</li> <li xml:lang="ca">Paquets d'adhesius - MSC2545</li>
<li xml:lang="ca-valencia">Paquets d'adhesius - MSC2545</li> <li xml:lang="ca-valencia">Paquets d'adhesius - MSC2545</li>
<li xml:lang="el">Πακέτα αυτοκόλλητων - MSC2545</li>
<li xml:lang="en-GB">Sticker Packs - MSC2545</li> <li xml:lang="en-GB">Sticker Packs - MSC2545</li>
<li xml:lang="eo">Glumark-Pakoj - MSC2545</li> <li xml:lang="eo">Glumark-Pakoj - MSC2545</li>
<li xml:lang="es">Paquetes de pegatinas - MSC2545</li> <li xml:lang="es">Paquetes de pegatinas - MSC2545</li>
@@ -236,7 +216,6 @@
<li xml:lang="fr">Paquets d'auto-collants - MSC2545</li> <li xml:lang="fr">Paquets d'auto-collants - MSC2545</li>
<li xml:lang="gl">Paquetes de adhesivos — MSC2545</li> <li xml:lang="gl">Paquetes de adhesivos — MSC2545</li>
<li xml:lang="he">חבילות מדבקות - MSC2545</li> <li xml:lang="he">חבילות מדבקות - MSC2545</li>
<li xml:lang="hi">स्टिकर पैक - MSC2545</li>
<li xml:lang="hu">Matricacsomagok - MSC2545</li> <li xml:lang="hu">Matricacsomagok - MSC2545</li>
<li xml:lang="ia">Etiquetta gummate (sticker) -MSC2545</li> <li xml:lang="ia">Etiquetta gummate (sticker) -MSC2545</li>
<li xml:lang="it">Pacchetti di adesivi - MSC2545</li> <li xml:lang="it">Pacchetti di adesivi - MSC2545</li>
@@ -247,21 +226,18 @@
<li xml:lang="nn">Klistremerke-pakkar  MSC2545</li> <li xml:lang="nn">Klistremerke-pakkar  MSC2545</li>
<li xml:lang="pl">Paczki naklejek - MSC2545</li> <li xml:lang="pl">Paczki naklejek - MSC2545</li>
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li> <li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
<li xml:lang="pt-BR">Pacotes de Stickers - MSC2545</li>
<li xml:lang="ro">Colecții de abțibilduri - MSC2545</li>
<li xml:lang="ru">Наборы стикеров — MSC2545</li> <li xml:lang="ru">Наборы стикеров — MSC2545</li>
<li xml:lang="sa">स्टिकर पैक - MSC2545</li>
<li xml:lang="sl">Sticker Packs - MSC2545</li> <li xml:lang="sl">Sticker Packs - MSC2545</li>
<li xml:lang="sv">Sticker Packs - MSC2545</li> <li xml:lang="sv">Sticker Packs - MSC2545</li>
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li> <li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
<li xml:lang="tr">Çıkartma Paketleri — MSC2545</li> <li xml:lang="tr">Çıkartma Paketleri — MSC2545</li>
<li xml:lang="uk">Пакунки наліпок - MSC2545</li> <li xml:lang="uk">Пакунки наліпок - MSC2545</li>
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
<li xml:lang="zh-TW">貼圖包 - MSC2545</li> <li xml:lang="zh-TW">貼圖包 - MSC2545</li>
<li>Location Events - MSC3488</li> <li>Location Events - MSC3488</li>
<li xml:lang="ar">موقع الأحداث - MSC3488</li> <li xml:lang="ar">موقع الأحداث - MSC3488</li>
<li xml:lang="ca">Esdeveniments d'ubicació - MSC3488</li> <li xml:lang="ca">Esdeveniments d'ubicació - MSC3488</li>
<li xml:lang="ca-valencia">Esdeveniments d'ubicació - MSC3488</li> <li xml:lang="ca-valencia">Esdeveniments d'ubicació - MSC3488</li>
<li xml:lang="el">Τοποθεσία γεγονότα - MSC3488</li>
<li xml:lang="en-GB">Location Events - MSC3488</li> <li xml:lang="en-GB">Location Events - MSC3488</li>
<li xml:lang="eo">Lokaj Eventoj - MSC3488</li> <li xml:lang="eo">Lokaj Eventoj - MSC3488</li>
<li xml:lang="es">Eventos de ubicación - MSC3488</li> <li xml:lang="es">Eventos de ubicación - MSC3488</li>
@@ -270,7 +246,6 @@
<li xml:lang="fr">Événements de lieu - MSC3488</li> <li xml:lang="fr">Événements de lieu - MSC3488</li>
<li xml:lang="gl">Localización de eventos — MSC3488</li> <li xml:lang="gl">Localización de eventos — MSC3488</li>
<li xml:lang="he">אירועי מקום - MSC3488</li> <li xml:lang="he">אירועי מקום - MSC3488</li>
<li xml:lang="hi">स्थान घटनाएँ - MSC3488</li>
<li xml:lang="hu">Események helyadatai - MSC3488</li> <li xml:lang="hu">Események helyadatai - MSC3488</li>
<li xml:lang="ia">Eventos de Location - MSC3488</li> <li xml:lang="ia">Eventos de Location - MSC3488</li>
<li xml:lang="it">Località eventi - MSC3488</li> <li xml:lang="it">Località eventi - MSC3488</li>
@@ -281,15 +256,13 @@
<li xml:lang="nn">Posisjonshendingar  MSC3488</li> <li xml:lang="nn">Posisjonshendingar  MSC3488</li>
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li> <li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
<li xml:lang="pt">Eventos com Localizações - MSC3488</li> <li xml:lang="pt">Eventos com Localizações - MSC3488</li>
<li xml:lang="pt-BR">Localização de eventos - MSC3488</li>
<li xml:lang="ro">Evenimente de amplasare - MSC3488</li>
<li xml:lang="ru">События местоположения — MSC3488</li> <li xml:lang="ru">События местоположения — MSC3488</li>
<li xml:lang="sa">स्थान घटनाएँ - MSC3488</li>
<li xml:lang="sl">Location Events - MSC3488</li> <li xml:lang="sl">Location Events - MSC3488</li>
<li xml:lang="sv">Location Events - MSC3488</li> <li xml:lang="sv">Location Events - MSC3488</li>
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li> <li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
<li xml:lang="tr">Konum Etkinlikleri — MSC3488</li> <li xml:lang="tr">Konum Etkinlikleri — MSC3488</li>
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li> <li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
<li xml:lang="zh-TW">位置事件 - MSC3488</li> <li xml:lang="zh-TW">位置事件 - MSC3488</li>
</ul> </ul>
</description> </description>
@@ -320,7 +293,6 @@
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value> <value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value> <value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value> <value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
<value key="KDE::supporters">Tanguy Fardet;[dabe](https://freeradical.zone/@dabe);[lengau](https://mastodon.world/@lengau);Joshua Strobl;Stuart Turton</value>
</custom> </custom>
<launchable type="desktop-id">org.kde.neochat.desktop</launchable> <launchable type="desktop-id">org.kde.neochat.desktop</launchable>
<screenshots> <screenshots>
@@ -331,7 +303,6 @@
<caption xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</caption> <caption xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</caption>
<caption xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</caption> <caption xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</caption>
<caption xml:lang="de">Hauptansicht mit Raumliste, Unterhaltung und Raum-Informationen</caption> <caption xml:lang="de">Hauptansicht mit Raumliste, Unterhaltung und Raum-Informationen</caption>
<caption xml:lang="el">Κύρια προβολή με λίστα δωματίων, συνομιλία και πληροφορίες δωματίων</caption>
<caption xml:lang="en-GB">Main view with room list, chat, and room information</caption> <caption xml:lang="en-GB">Main view with room list, chat, and room information</caption>
<caption xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</caption> <caption xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</caption>
<caption xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</caption> <caption xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</caption>
@@ -340,7 +311,6 @@
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption> <caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption> <caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
<caption xml:lang="he">תצוגה ראשית עם רשימת חדרים, צ׳אט ופרטי חדר</caption> <caption xml:lang="he">תצוגה ראשית עם רשימת חדרים, צ׳אט ופרטי חדר</caption>
<caption xml:lang="hi">कमरे की सूची, चैट और कमरे की जानकारी के साथ मुख्य दृश्य</caption>
<caption xml:lang="hu">A fő nézet a szobalistával, csevegéssel és szobainformációkkal</caption> <caption xml:lang="hu">A fő nézet a szobalistával, csevegéssel és szobainformációkkal</caption>
<caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption> <caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption>
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption> <caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
@@ -351,15 +321,13 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption> <caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption> <caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption> <caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption> <caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption> <caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
<caption xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</caption> <caption xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</caption>
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption> <caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption> <caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption> <caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption> <caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot> </screenshot>
<screenshot type="default"> <screenshot type="default">
@@ -369,7 +337,6 @@
<caption xml:lang="ca">Descobriu comunitats noves amb els espais de Matrix</caption> <caption xml:lang="ca">Descobriu comunitats noves amb els espais de Matrix</caption>
<caption xml:lang="ca-valencia">Descobriu comunitats noves amb els espais de Matrix</caption> <caption xml:lang="ca-valencia">Descobriu comunitats noves amb els espais de Matrix</caption>
<caption xml:lang="de">Neue Gemeinschaften mit den Umgebungen von Matrix erkunden</caption> <caption xml:lang="de">Neue Gemeinschaften mit den Umgebungen von Matrix erkunden</caption>
<caption xml:lang="el">Ανακαλύψτε νέες κοινότητες με το Matrix Spaces</caption>
<caption xml:lang="en-GB">Discover new communities with Matrix Spaces</caption> <caption xml:lang="en-GB">Discover new communities with Matrix Spaces</caption>
<caption xml:lang="eo">Malkovru novajn komunumojn per Matrix Spaces</caption> <caption xml:lang="eo">Malkovru novajn komunumojn per Matrix Spaces</caption>
<caption xml:lang="es">Descubra nuevas comunidades con los espacios de Matrix</caption> <caption xml:lang="es">Descubra nuevas comunidades con los espacios de Matrix</caption>
@@ -378,25 +345,21 @@
<caption xml:lang="fr">Découvrez de nouvelles communautés avec les espaces sous Matrix</caption> <caption xml:lang="fr">Découvrez de nouvelles communautés avec les espaces sous Matrix</caption>
<caption xml:lang="gl">Descubra novas comunidades dos espazos de Matrix.</caption> <caption xml:lang="gl">Descubra novas comunidades dos espazos de Matrix.</caption>
<caption xml:lang="he">אפשר להיחשף לקהילות חדשות דרך Matrix Spaces</caption> <caption xml:lang="he">אפשר להיחשף לקהילות חדשות דרך Matrix Spaces</caption>
<caption xml:lang="hi">मैट्रिक्स स्पेस के साथ नए समुदायों की खोज करें</caption>
<caption xml:lang="hu">Fedezzen fel új közösségeket a Matrix Terek segítségével</caption> <caption xml:lang="hu">Fedezzen fel új közösségeket a Matrix Terek segítségével</caption>
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption> <caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption>
<caption xml:lang="it">Scopri nuove comunità con Matrix Spaces</caption> <caption xml:lang="it">Scopri nuove comunità con Matrix Spaces</caption>
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption> <caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
<caption xml:lang="ko">Matrix 스페이스에서 새로운 커뮤니티 탐험</caption>
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption> <caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption> <caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption> <caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption> <caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
<caption xml:lang="pt-BR">Descubra novas comunidades com os Espaços Matrix</caption>
<caption xml:lang="ro">Descoperiți comunități noi cu Spații Matrix</caption>
<caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption> <caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption>
<caption xml:lang="sa">Matrix Spaces इत्यनेन सह नूतनानां समुदायानाम् अन्वेषणं कुर्वन्तु</caption>
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption> <caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
<caption xml:lang="sv">Upptäck nya gemenskaper med Matrix Spaces</caption> <caption xml:lang="sv">Upptäck nya gemenskaper med Matrix Spaces</caption>
<caption xml:lang="ta">மேட்ரிக்ஸு இடங்களின் மூலம் புதிய சமூகங்களைக் கண்டுபிடிக்கலாம்</caption> <caption xml:lang="ta">மேட்ரிக்ஸு இடங்களின் மூலம் புதிய சமூகங்களைக் கண்டுபிடிக்கலாம்</caption>
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption> <caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption>
<caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption> <caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption>
<caption xml:lang="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
<caption xml:lang="zh-TW">利用 Matrix 聊天空間發現新的社群</caption> <caption xml:lang="zh-TW">利用 Matrix 聊天空間發現新的社群</caption>
</screenshot> </screenshot>
<!-- <!--
@@ -413,7 +376,6 @@
<caption xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</caption> <caption xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</caption>
<caption xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</caption> <caption xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</caption>
<caption xml:lang="de">Hauptansicht mit Raumliste, Unterhaltung und Raum-Informationen</caption> <caption xml:lang="de">Hauptansicht mit Raumliste, Unterhaltung und Raum-Informationen</caption>
<caption xml:lang="el">Κύρια προβολή με λίστα δωματίων, συνομιλία και πληροφορίες δωματίων</caption>
<caption xml:lang="en-GB">Main view with room list, chat, and room information</caption> <caption xml:lang="en-GB">Main view with room list, chat, and room information</caption>
<caption xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</caption> <caption xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</caption>
<caption xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</caption> <caption xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</caption>
@@ -422,7 +384,6 @@
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption> <caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption> <caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
<caption xml:lang="he">תצוגה ראשית עם רשימת חדרים, צ׳אט ופרטי חדר</caption> <caption xml:lang="he">תצוגה ראשית עם רשימת חדרים, צ׳אט ופרטי חדר</caption>
<caption xml:lang="hi">कमरे की सूची, चैट और कमरे की जानकारी के साथ मुख्य दृश्य</caption>
<caption xml:lang="hu">A fő nézet a szobalistával, csevegéssel és szobainformációkkal</caption> <caption xml:lang="hu">A fő nézet a szobalistával, csevegéssel és szobainformációkkal</caption>
<caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption> <caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption>
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption> <caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
@@ -433,15 +394,13 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption> <caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption> <caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption> <caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption> <caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption> <caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
<caption xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</caption> <caption xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</caption>
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption> <caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption> <caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption> <caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption> <caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot> </screenshot>
<screenshot environment="windows"> <screenshot environment="windows">
@@ -452,7 +411,6 @@
<caption xml:lang="ca-valencia">Pantalla d'inici de sessió</caption> <caption xml:lang="ca-valencia">Pantalla d'inici de sessió</caption>
<caption xml:lang="cs">Přihlašovací obrazovka</caption> <caption xml:lang="cs">Přihlašovací obrazovka</caption>
<caption xml:lang="de">Anmeldebildschirm</caption> <caption xml:lang="de">Anmeldebildschirm</caption>
<caption xml:lang="el">Οθόνη εισόδου</caption>
<caption xml:lang="en-GB">Login screen</caption> <caption xml:lang="en-GB">Login screen</caption>
<caption xml:lang="eo">Ensaluta ekrano</caption> <caption xml:lang="eo">Ensaluta ekrano</caption>
<caption xml:lang="es">Pantalla de inicio de sesión</caption> <caption xml:lang="es">Pantalla de inicio de sesión</caption>
@@ -461,7 +419,6 @@
<caption xml:lang="fr">Écran de connexion</caption> <caption xml:lang="fr">Écran de connexion</caption>
<caption xml:lang="gl">Pantalla de identificación.</caption> <caption xml:lang="gl">Pantalla de identificación.</caption>
<caption xml:lang="he">מסך כניסה</caption> <caption xml:lang="he">מסך כניסה</caption>
<caption xml:lang="hi">लॉगिन स्क्रीन</caption>
<caption xml:lang="hu">Bejelentkező képernyő</caption> <caption xml:lang="hu">Bejelentkező képernyő</caption>
<caption xml:lang="ia">Schermo de accesso</caption> <caption xml:lang="ia">Schermo de accesso</caption>
<caption xml:lang="it">Schermata di accesso</caption> <caption xml:lang="it">Schermata di accesso</caption>
@@ -472,15 +429,13 @@
<caption xml:lang="nn">Innloggingsbilete</caption> <caption xml:lang="nn">Innloggingsbilete</caption>
<caption xml:lang="pl">Ekran logowania</caption> <caption xml:lang="pl">Ekran logowania</caption>
<caption xml:lang="pt">Ecrã de autenticação</caption> <caption xml:lang="pt">Ecrã de autenticação</caption>
<caption xml:lang="pt-BR">Tela de login</caption>
<caption xml:lang="ro">Ecran de autentificare</caption>
<caption xml:lang="ru">Окно входа</caption> <caption xml:lang="ru">Окно входа</caption>
<caption xml:lang="sa">लॉगिन् स्क्रीन</caption>
<caption xml:lang="sl">Prijavni zaslon</caption> <caption xml:lang="sl">Prijavni zaslon</caption>
<caption xml:lang="sv">Inloggningsfönster</caption> <caption xml:lang="sv">Inloggningsfönster</caption>
<caption xml:lang="ta">நுழைவுத் திரை</caption> <caption xml:lang="ta">நுழைவுத் திரை</caption>
<caption xml:lang="tr">Oturum açma ekranı</caption> <caption xml:lang="tr">Oturum açma ekranı</caption>
<caption xml:lang="uk">Вікно входу</caption> <caption xml:lang="uk">Вікно входу</caption>
<caption xml:lang="x-test">xxLogin screenxx</caption>
<caption xml:lang="zh-TW">登入畫面</caption> <caption xml:lang="zh-TW">登入畫面</caption>
</screenshot> </screenshot>
</screenshots> </screenshots>
@@ -488,18 +443,6 @@
<content_attribute id="social-chat">intense</content_attribute> <content_attribute id="social-chat">intense</content_attribute>
</content_rating> </content_rating>
<releases> <releases>
<release version="25.08.3" date="2025-11-06"/>
<release version="25.08.2" date="2025-10-09"/>
<release version="25.08.1" date="2025-09-11"/>
<release version="25.08.0" date="2025-08-14"/>
<release version="25.04.3" date="2025-07-03"/>
<release version="25.04.2" date="2025-06-05"/>
<release version="25.04.1" date="2025-05-08"/>
<release version="25.04.0" date="2025-04-17"/>
<release version="24.12.3" date="2025-03-06"/>
<release version="24.12.2" date="2025-02-06"/>
<release version="24.12.1" date="2025-01-09"/>
<release version="24.12.0" date="2024-12-12"/>
<release version="24.08.3" date="2024-11-07"/> <release version="24.08.3" date="2024-11-07"/>
<release version="24.08.2" date="2024-10-10"/> <release version="24.08.2" date="2024-10-10"/>
<release version="24.08.1" date="2024-09-12"/> <release version="24.08.1" date="2024-09-12"/>
@@ -674,9 +617,6 @@
<url>https://carlschwan.eu/2020/12/23/announcing-neochat-1.0-the-kde-matrix-client/</url> <url>https://carlschwan.eu/2020/12/23/announcing-neochat-1.0-the-kde-matrix-client/</url>
</release> </release>
</releases> </releases>
<requires>
<display_length compare="ge">360</display_length>
</requires>
<branding> <branding>
<color type="primary" scheme_preference="light">#a6e4f3</color> <color type="primary" scheme_preference="light">#a6e4f3</color>
<color type="primary" scheme_preference="dark">#235670</color> <color type="primary" scheme_preference="dark">#235670</color>

View File

@@ -15,11 +15,11 @@ Name[en_GB]=NeoChat
Name[eo]=NeoChat Name[eo]=NeoChat
Name[es]=NeoChat Name[es]=NeoChat
Name[eu]=NeoChat Name[eu]=NeoChat
Name[fa]=نئوچت
Name[fi]=NeoChat Name[fi]=NeoChat
Name[fr]=NeoChat Name[fr]=NeoChat
Name[gl]=NeoChat Name[gl]=NeoChat
Name[he]=NeoChat Name[he]=NeoChat
Name[hi]=नियोचैट
Name[hu]=NeoChat Name[hu]=NeoChat
Name[ia]=Neochat Name[ia]=Neochat
Name[id]=NeoChat Name[id]=NeoChat
@@ -37,13 +37,13 @@ Name[pt]=NeoChat
Name[pt_BR]=NeoChat Name[pt_BR]=NeoChat
Name[ro]=NeoChat Name[ro]=NeoChat
Name[ru]=NeoChat Name[ru]=NeoChat
Name[sa]=नवचैट्
Name[sk]=NeoChat Name[sk]=NeoChat
Name[sl]=NeoChat Name[sl]=NeoChat
Name[sv]=NeoChat Name[sv]=NeoChat
Name[ta]=நியோச்சாட் Name[ta]=நியோச்சாட்
Name[tr]=NeoChat Name[tr]=NeoChat
Name[uk]=NeoChat Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat Name[zh_TW]=NeoChat
GenericName=Matrix Client GenericName=Matrix Client
@@ -62,7 +62,6 @@ GenericName[fi]=Matrix-asiakas
GenericName[fr]=Client « Matrix » GenericName[fr]=Client « Matrix »
GenericName[gl]=Cliente de Matrix GenericName[gl]=Cliente de Matrix
GenericName[he]=לקוח Matrix GenericName[he]=לקוח Matrix
GenericName[hi]=मैट्रिक्स क्लाइंट
GenericName[hu]=Matrix kliens GenericName[hu]=Matrix kliens
GenericName[ia]=Cliente de Matrice GenericName[ia]=Cliente de Matrice
GenericName[id]=Klien Matrix GenericName[id]=Klien Matrix
@@ -80,48 +79,56 @@ GenericName[pt]=Cliente de Matrix
GenericName[pt_BR]=Cliente Matrix GenericName[pt_BR]=Cliente Matrix
GenericName[ro]=Client Matrix GenericName[ro]=Client Matrix
GenericName[ru]=Клиент Matrix GenericName[ru]=Клиент Matrix
GenericName[sa]=मैट्रिक्स क्लाइंट
GenericName[sk]=Matrix Client GenericName[sk]=Matrix Client
GenericName[sl]=Odjemalec Matrix GenericName[sl]=Odjemalec Matrix
GenericName[sv]=Matrix-klient GenericName[sv]=Matrix-klient
GenericName[ta]=Matrix வாங்கி GenericName[ta]=Matrix வாங்கி
GenericName[tr]=Matrix İstemcisi GenericName[tr]=Matrix İstemcisi
GenericName[uk]=Клієнт Matrix GenericName[uk]=Клієнт Matrix
GenericName[x-test]=xxMatrix Clientxx
GenericName[zh_CN]=Matrix 客户端 GenericName[zh_CN]=Matrix 客户端
GenericName[zh_TW]=Matrix 用戶端 GenericName[zh_TW]=Matrix 用戶端
Comment=Chat on Matrix Comment=Client for the Matrix protocol
Comment[ar]=دردش على ماتركس Comment[ar]=عميل لميفاق ماتركس
Comment[ca]=Xat a Matrix Comment[az]=Matrix protokolu üçün müştəri
Comment[ca@valencia]=Xat a Matrix Comment[ca]=Client per al protocol Matrix
Comment[de]=Über Matrix unterhalten Comment[ca@valencia]=Client per al protocol Matrix
Comment[en_GB]=Chat on Matrix Comment[de]=Programm für das Matrix-Protokoll
Comment[eo]=Babilo en Matrix Comment[el]=Πελάτης για το πρωτόκολλο Matrix
Comment[es]=Chat en Matrix Comment[en_GB]=Client for the Matrix protocol
Comment[eu]=Berriketa Matrix-en Comment[eo]=Kliento por la Matrix-protokolo
Comment[fi]=Keskustele Matrixissä Comment[es]=Cliente para el protocolo Matrix
Comment[fr]=Clavarder sur Matrix Comment[eu]=Matrix protokolorako bezeroa
Comment[gl]=Charle en Matrix Comment[fi]=Asiakas Matrix-yhteyskäytännölle
Comment[he]=התכתבות דרך Matrix Comment[fr]=Client pour le protocole « Matrix »
Comment[hi]=मैट्रिक्स पर चैट करें Comment[gl]=Cliente para o protocolo Matrix.
Comment[hu]=Csevegés Matrixon Comment[he]=לקוח לפרוטוקול Matrix
Comment[ia]=Conversation en ditecto sur Matrix Comment[hu]=Kliens a Matrix protokollhoz
Comment[it]= su Matrix Comment[ia]=Cliente per le protocollo de Matrix
Comment[ka]=ჩატი Matrix-ზე Comment[id]=Klien untuk protokol Matrix
Comment[ko]=Matrix에서 대화하기 Comment[ie]=Un cliente del protocol Matrix
Comment[lv]=Tērzējiet „Matrix“ tīklā Comment[it]=Client per il protocollo Matrix
Comment[nl]=Chat op Matrix Comment[ka]=კლიენტი Matrix-ის პროტოკოლისთვის
Comment[pl]=Rozmawiaj na Matriksie Comment[ko]=Matrix 프로토콜용 클라이언트
Comment[pt_BR]=Bate papo na Matrix Comment[lt]=Matrix protokolo kliento programa
Comment[ro]=Discutați pe Matrix Comment[lv]=Klients „Matrix“ protokolam
Comment[ru]=Общение в Matrix Comment[nl]=Client voor het Matrix-protocol
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु Comment[nn]=Klient for Matrix-protokollen
Comment[sl]=Klepet na Matrixu Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
Comment[sv]=Chatta på Matrix Comment[pl]=Program obsługi protokołu Matriksa
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும் Comment[pt]=Cliente para o protocolo Matrix
Comment[tr]=Matrix üzerinde sohbet edin Comment[pt_BR]=Cliente para o protocolo Matrix
Comment[uk]=Спілкування у Matrix Comment[ro]=Client pentru protocolul Matrix
Comment[zh_CN]= Matrix 上聊天 Comment[ru]=Клиент для протокола Matrix
Comment[zh_TW]= Matrix 上聊天 Comment[sk]=Klient protokolu Matrix
Comment[sl]=Odjemalec za protokol Matrix
Comment[sv]=Klient för protokollet Matrix
Comment[ta]=Matrix நெறிமுறைக்கான வாங்கி
Comment[tr]=Matrix protokolü için istemci
Comment[uk]=Клієнт протоколу Matrix
Comment[x-test]=xxClient for the Matrix protocolxx
Comment[zh_CN]=为 Matrix 协议打造的客户端
Comment[zh_TW]=Matrix 通訊協定的用戶端
MimeType=x-scheme-handler/matrix; MimeType=x-scheme-handler/matrix;
Exec=neochat %u Exec=neochat %u
Terminal=false Terminal=false

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

5383
po/fa/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

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,122 +0,0 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
<!ENTITY % Slovenian "INCLUDE">
]>
<!--
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<refentry lang="&language;">
<refentryinfo>
<title
>Uporabniški priročnik za NeoChat</title>
<author
><firstname
>Carl</firstname
><surname
>Schwan</surname
> <contrib
>Stran z navodili za NeoChat.</contrib
> <email
>carl@carlschwan.eu</email
></author>
<date
>01.11.2022</date>
<releaseinfo
>22,09</releaseinfo>
<productname
>NeoChat</productname>
</refentryinfo>
<refmeta>
<refentrytitle>
<command
>neochat</command>
</refentrytitle>
<manvolnum
>1</manvolnum>
</refmeta>
<refnamediv>
<refname
>neochat</refname>
<refpurpose
>Odjemalec za interakcijo s protokolom za matrično sporočanje</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
>Opis</title>
<para
><command
>neochat</command
> je aplikacija za klepet za matrični protokol, ki deluje na namizju in mobilni napravi. </para>
</refsect1>
<refsect1 id="options"
><title
>Možnosti</title>
<variablelist>
<varlistentry>
<term
><option
>URI</option
></term>
<listitem>
<para
>Uri matrike za uporabnika ali sobo. npr. matrix:u/user:example.org in matrix:r/root:example.org. Tako bo NeoChat poskušal odpreti dano sobo ali pogovor. </para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="bug">
<title
>Poročanje o napakah</title>
<para
>Napake in zahteve po funkcijah lahko prijavite na <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General"
>https://bugs.kde.org/enter_bug.cgi? product=NeoChat&amp;component=General</ulink
></para>
</refsect1>
<refsect1>
<title
>Poglej tudi</title>
<simplelist>
<member
>Seznam pogostih vprašanj o Matrix <ulink url="https://matrix.org/faq/"
>https://matrix.org/faq/</ulink
> </member>
<member
>kf5options(7)</member>
<member
>qt5options(7)</member>
</simplelist>
</refsect1>
<refsect1 id="copyright"
><title
>Avtorske pravice</title>
<para
>Avtorske pravice &copy; 2020-2022 Tobias Fella </para>
<para
>Avtorske pravice &copy; 2020-2022 Carl Schwan </para>
<para
>Licenca: GNU General Public različica 3 ali novejša &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

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

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,9 +1,9 @@
# SPDX-FileCopyrightText: 2024-2025 Scarlett Moore <sgmoore@kde.org> # SPDX-FileCopyrightText: 2024 Scarlett Moore <sgmoore@kde.org>
# #
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
--- ---
name: neochat name: neochat
base: core24 base: core22
adopt-info: neochat adopt-info: neochat
grade: stable grade: stable
confinement: strict confinement: strict
@@ -24,16 +24,9 @@ apps:
- network-manager-observe - network-manager-observe
- password-manager-service - password-manager-service
- accounts-service - accounts-service
environment:
QT_PLUGIN_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/plugins/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/plugins"
QML_IMPORT_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml:/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml"
compression: lzo compression: lzo
package-repositories:
- type: apt
ppa: ubuntu-toolchain-r/test
slots: slots:
session-dbus-interface: session-dbus-interface:
interface: dbus interface: dbus
@@ -44,12 +37,11 @@ parts:
olm: olm:
source: https://gitlab.matrix.org/matrix-org/olm.git source: https://gitlab.matrix.org/matrix-org/olm.git
source-depth: 1 source-depth: 1
source-tag: '3.2.16' source-tag: '3.2.12'
plugin: cmake plugin: cmake
cmake-parameters: cmake-parameters:
- -DCMAKE_BUILD_TYPE=Release - -DCMAKE_BUILD_TYPE=Release
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_POLICY_VERSION_MINIMUM=3.5
prime: prime:
- -usr/include - -usr/include
- -usr/lib/*/pkgconfig - -usr/lib/*/pkgconfig
@@ -84,8 +76,7 @@ parts:
source-depth: 1 source-depth: 1
plugin: cmake plugin: cmake
build-environment: build-environment:
- PATH: /snap/bin:${PATH} - PKG_CONFIG_PATH: $CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET/pkgconfig:$PKG_CONFIG_PATH
- PKG_CONFIG_PATH: "$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET/pkgconfig:$PKG_CONFIG_PATH"
cmake-parameters: cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release - -DCMAKE_BUILD_TYPE=Release
@@ -101,13 +92,9 @@ parts:
- olm - olm
- qtkeychain - qtkeychain
source: https://github.com/quotient-im/libQuotient.git source: https://github.com/quotient-im/libQuotient.git
source-tag: 0.9.2 source-tag: 0.9.1
source-depth: 1 source-depth: 1
plugin: cmake plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
build-snaps:
- cmake
build-packages: build-packages:
- libssl-dev - libssl-dev
cmake-parameters: cmake-parameters:
@@ -126,10 +113,6 @@ parts:
source-tag: 'v0.3.0' source-tag: 'v0.3.0'
source-depth: 1 source-depth: 1
plugin: cmake plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
cmake-parameters: cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release - -DCMAKE_BUILD_TYPE=Release
@@ -140,32 +123,16 @@ parts:
- -usr/lib/*/pkgconfig - -usr/lib/*/pkgconfig
- -usr/lib/*/cmake - -usr/lib/*/cmake
kunifiedpush:
source: https://invent.kde.org/libraries/kunifiedpush.git
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
- -DBUILD_TESTING=OFF
neochat: neochat:
after: after:
- qtkeychain - qtkeychain
- libquotient - libquotient
- kquickimageeditor - kquickimageeditor
- kunifiedpush
parse-info: parse-info:
- usr/share/metainfo/org.kde.neochat.appdata.xml - usr/share/metainfo/org.kde.neochat.appdata.xml
source: . source: .
source-type: local
plugin: cmake plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
build-packages: build-packages:
- cmark - cmark
- libcmark-dev - libcmark-dev
@@ -189,12 +156,3 @@ parts:
prime: prime:
- usr/lib/*/libcmark.so* - usr/lib/*/libcmark.so*
gpu-2404:
after: [neochat]
source: https://github.com/canonical/gpu-snap.git
plugin: dump
override-prime: |
craftctl default
${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
prime:
- bin/gpu-2404-wrapper

View File

@@ -1,18 +1,563 @@
# SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com> # 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 <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE) if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE)
add_subdirectory(purpose) add_subdirectory(purpose)
endif() endif()
add_subdirectory(libneochat) add_library(neochat STATIC
add_subdirectory(login) controller.cpp
add_subdirectory(rooms) controller.h
add_subdirectory(roominfo) actionshandler.cpp
add_subdirectory(messagecontent) actionshandler.h
add_subdirectory(timeline) models/emojimodel.cpp
add_subdirectory(spaces) models/emojimodel.h
add_subdirectory(chatbar) emojitones.cpp
emojitones.h
models/customemojimodel.cpp
models/customemojimodel.h
clipboard.cpp
clipboard.h
models/messageeventmodel.cpp
models/messageeventmodel.h
models/messagefiltermodel.cpp
models/messagefiltermodel.h
models/roomlistmodel.cpp
models/roomlistmodel.h
models/sortfilterspacelistmodel.cpp
models/sortfilterspacelistmodel.h
models/accountemoticonmodel.cpp
models/accountemoticonmodel.h
spacehierarchycache.cpp
spacehierarchycache.h
roommanager.cpp
roommanager.h
neochatroom.cpp
neochatroom.h
models/userlistmodel.cpp
models/userlistmodel.h
models/userfiltermodel.cpp
models/userfiltermodel.h
models/publicroomlistmodel.cpp
models/publicroomlistmodel.h
models/spacechildrenmodel.cpp
models/spacechildrenmodel.h
models/spacechildsortfiltermodel.cpp
models/spacechildsortfiltermodel.h
models/spacetreeitem.cpp
models/spacetreeitem.h
models/userdirectorylistmodel.cpp
models/userdirectorylistmodel.h
models/pushrulemodel.cpp
models/pushrulemodel.h
models/emoticonfiltermodel.cpp
models/emoticonfiltermodel.h
notificationsmanager.cpp
notificationsmanager.h
models/sortfilterroomlistmodel.cpp
models/sortfilterroomlistmodel.h
models/roomtreemodel.cpp
models/roomtreemodel.h
chatdocumenthandler.cpp
chatdocumenthandler.h
models/devicesmodel.cpp
models/devicesmodel.h
models/devicesproxymodel.cpp
filetype.cpp
filetype.h
login.cpp
login.h
models/webshortcutmodel.cpp
models/webshortcutmodel.h
blurhash.cpp
blurhash.h
blurhashimageprovider.cpp
blurhashimageprovider.h
models/mediamessagefiltermodel.cpp
models/mediamessagefiltermodel.h
urlhelper.cpp
urlhelper.h
windowcontroller.cpp
windowcontroller.h
linkpreviewer.cpp
linkpreviewer.h
models/completionmodel.cpp
models/completionmodel.h
models/completionproxymodel.cpp
models/completionproxymodel.h
models/actionsmodel.cpp
models/actionsmodel.h
models/serverlistmodel.cpp
models/serverlistmodel.h
models/statemodel.cpp
models/statemodel.h
models/statefiltermodel.cpp
models/statefiltermodel.h
filetransferpseudojob.cpp
filetransferpseudojob.h
models/searchmodel.cpp
models/searchmodel.h
texthandler.cpp
texthandler.h
logger.cpp
logger.h
models/stickermodel.cpp
models/stickermodel.h
models/imagepacksmodel.cpp
models/imagepacksmodel.h
events/imagepackevent.cpp
events/imagepackevent.h
events/joinrulesevent.cpp
events/joinrulesevent.h
models/reactionmodel.cpp
models/reactionmodel.h
delegatesizehelper.cpp
delegatesizehelper.h
models/livelocationsmodel.cpp
models/livelocationsmodel.h
models/locationsmodel.cpp
models/locationsmodel.h
locationhelper.cpp
locationhelper.h
events/pollevent.cpp
pollhandler.cpp
utils.h
utils.cpp
registration.cpp
neochatconnection.cpp
neochatconnection.h
jobs/neochatdeactivateaccountjob.cpp
jobs/neochatdeactivateaccountjob.h
jobs/neochatdeletedevicejob.cpp
jobs/neochatdeletedevicejob.h
jobs/neochatchangepasswordjob.cpp
jobs/neochatchangepasswordjob.h
jobs/neochatgetcommonroomsjob.cpp
jobs/neochatgetcommonroomsjob.h
mediasizehelper.cpp
mediasizehelper.h
eventhandler.cpp
enums/delegatetype.h
roomlastmessageprovider.cpp
roomlastmessageprovider.h
chatbarcache.cpp
chatbarcache.h
colorschemer.cpp
colorschemer.h
models/notificationsmodel.cpp
models/notificationsmodel.h
models/timelinemodel.cpp
models/timelinemodel.h
enums/pushrule.h
models/itinerarymodel.cpp
models/itinerarymodel.h
proxycontroller.cpp
proxycontroller.h
models/linemodel.cpp
models/linemodel.h
events/locationbeaconevent.h
events/widgetevent.h
enums/messagecomponenttype.h
models/messagecontentmodel.cpp
models/messagecontentmodel.h
enums/neochatroomtype.h
models/sortfilterroomtreemodel.cpp
models/sortfilterroomtreemodel.h
mediamanager.cpp
mediamanager.h
models/statekeysmodel.cpp
models/statekeysmodel.h
sharehandler.cpp
sharehandler.h
models/roomtreeitem.cpp
models/roomtreeitem.h
foreigntypes.h
models/threepidmodel.cpp
models/threepidmodel.h
threepidaddhelper.cpp
threepidaddhelper.h
jobs/neochatadd3pidjob.cpp
jobs/neochatadd3pidjob.h
identityserverhelper.cpp
identityserverhelper.h
enums/powerlevel.cpp
enums/powerlevel.h
models/permissionsmodel.cpp
models/permissionsmodel.h
threepidbindhelper.cpp
threepidbindhelper.h
models/readmarkermodel.cpp
models/readmarkermodel.h
neochatroommember.cpp
neochatroommember.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction
)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
QML_FILES
qml/Main.qml
qml/AccountMenu.qml
qml/ExploreComponent.qml
qml/ExploreComponentMobile.qml
qml/ContextMenu.qml
qml/CollapsedRoomDelegate.qml
qml/RoomDelegate.qml
qml/RoomListPage.qml
qml/SpaceListContextMenu.qml
qml/UserInfo.qml
qml/UserInfoDesktop.qml
qml/RoomPage.qml
qml/ExploreRoomsPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/ImageEditorPage.qml
qml/NeochatMaximizeComponent.qml
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/HoverActions.qml
qml/AttachmentPane.qml
qml/QuickFormatBar.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
qml/PowerLevelDialog.qml
qml/Message.qml
qml/EmojiItem.qml
qml/EmojiRow.qml
qml/EmojiSas.qml
qml/ConfirmDeactivateAccountDialog.qml
qml/VerificationCanceled.qml
qml/GlobalMenu.qml
qml/EditMenu.qml
qml/MessageDelegateContextMenu.qml
qml/FileDelegateContextMenu.qml
qml/MessageSourceSheet.qml
qml/ReportSheet.qml
qml/ConfirmEncryptionDialog.qml
qml/RemoveSheet.qml
qml/BanSheet.qml
qml/RoomSearchPage.qml
qml/LocationChooser.qml
qml/TimelineView.qml
qml/InvitationView.qml
qml/AvatarTabButton.qml
qml/SpaceDrawer.qml
qml/OsmLocationPlugin.qml
qml/FullScreenMap.qml
qml/LocationsPage.qml
qml/LocationMapItem.qml
qml/RoomDrawer.qml
qml/RoomDrawerPage.qml
qml/DirectChatDrawerHeader.qml
qml/GroupChatDrawerHeader.qml
qml/RoomInformation.qml
qml/RoomMedia.qml
qml/ChooseRoomDialog.qml
qml/SpaceHomePage.qml
qml/SpaceHierarchyDelegate.qml
qml/RemoveChildDialog.qml
qml/SelectParentDialog.qml
qml/QrCodeMaximizeComponent.qml
qml/SelectSpacesDialog.qml
qml/NotificationsView.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
qml/UserSearchPage.qml
qml/ManualUserDialog.qml
qml/RecommendedSpaceDialog.qml
qml/RoomTreeSection.qml
qml/DelegateContextMenu.qml
qml/ShareDialog.qml
qml/UnlockSSSSDialog.qml
qml/QrScannerPage.qml
qml/JoinRoomDialog.qml
qml/ConfirmUrlDialog.qml
qml/AccountSwitchDialog.qml
qml/ConfirmLeaveDialog.qml
qml/CodeMaximizeComponent.qml
qml/EditStateDialog.qml
qml/ConsentDialog.qml
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
DEPENDENCIES
QtCore
QtQuick
IMPORTS
org.kde.neochat.timeline
org.kde.neochat.settings
org.kde.neochat.devtools
org.kde.neochat.login
org.kde.neochat.chatbar
)
add_subdirectory(settings) add_subdirectory(settings)
add_subdirectory(timeline)
add_subdirectory(devtools) add_subdirectory(devtools)
add_subdirectory(app) add_subdirectory(login)
add_subdirectory(chatbar)
if(UNIX)
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
else()
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
endif()
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
ecm_qt_declare_logging_category(neochat
HEADER "messageeventmodel_logging.h"
IDENTIFIER "MessageEvent"
CATEGORY_NAME "org.kde.neochat.messageeventmodel"
DESCRIPTION "Neochat: messageeventmodel"
DEFAULT_SEVERITY Info
EXPORT NEOCHAT
)
ecm_qt_declare_logging_category(neochat
HEADER "publicroomlist_logging.h"
IDENTIFIER "PublicRoomList"
CATEGORY_NAME "org.kde.neochat.publicroomlistmodel"
DESCRIPTION "Neochat: publicroomlistmodel"
DEFAULT_SEVERITY Info
EXPORT NEOCHAT
)
ecm_qt_declare_logging_category(neochat
HEADER "eventhandler_logging.h"
IDENTIFIER "EventHandling"
CATEGORY_NAME "org.kde.neochat.eventhandler"
DEFAULT_SEVERITY Info
)
ecm_qt_declare_logging_category(neochat
HEADER "chatdocumenthandler_logging.h"
IDENTIFIER "ChatDocumentHandling"
CATEGORY_NAME "org.kde.neochat.chatdocumenthandler"
DEFAULT_SEVERITY Info
)
add_executable(neochat-app
main.cpp
)
if(TARGET Qt::WebView)
target_link_libraries(neochat-app PUBLIC Qt::WebView)
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
endif()
target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat-app PRIVATE
neochat
)
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
if(NOT ANDROID)
if (NOT WIN32 AND NOT APPLE)
target_sources(neochat PRIVATE trayicon_sni.cpp trayicon_sni.h)
target_link_libraries(neochat PRIVATE KF6::StatusNotifierItem)
else()
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
endif()
target_link_libraries(neochat PUBLIC KF6::WindowSystem ICU::uc)
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
target_compile_definitions(neochat PUBLIC -DHAVE_ICU)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp)
endif()
else()
target_compile_definitions(neochat PUBLIC -DHAVE_X11=0)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin chatbarplugin)
target_link_libraries(neochat PUBLIC
Qt::Core
Qt::Quick
Qt::Qml
Qt::Gui
Qt::Multimedia
Qt::Network
Qt::QuickControls2
KF6::I18n
KF6::Kirigami
KF6::Notifications
KF6::ConfigCore
KF6::ConfigGui
KF6::CoreAddons
KF6::SonnetCore
KF6::ColorScheme
KF6::ItemModels
QuotientQt6
cmark::cmark
QCoro::Core
QCoro::Network
)
if (TARGET KF6::Crash)
target_link_libraries(neochat PUBLIC KF6::Crash)
endif()
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
if(NEOCHAT_FLATPAK)
target_compile_definitions(neochat PUBLIC NEOCHAT_FLATPAK)
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
"arrow-down"
"arrow-up"
"checkmark"
"help-about"
"im-user"
"im-invisible-user"
"im-kick-user"
"mail-attachment"
"dialog-cancel"
"preferences-desktop-emoticons"
"document-open"
"document-save"
"document-send"
"dialog-close"
"edit-delete-remove"
"code-context"
"document-edit"
"list-user-add"
"list-add-user"
"user-others"
"media-playback-pause"
"media-playback-start"
"media-playback-stop"
"go-previous"
"go-up"
"go-down"
"list-add"
"irc-join-channel"
"settings-configure"
"configure"
"rating"
"rating-unrated"
"search"
"mail-replied-symbolic"
"edit-clear"
"edit-copy"
"gtk-quit"
"compass"
"computer"
"network-connect"
"list-remove-user"
"org.kde.neochat"
"preferences-system-users"
"preferences-desktop-theme-global"
"notifications"
"notifications-disabled"
"audio-volume-high"
"audio-volume-muted"
"draw-highlight"
"zoom-in"
"zoom-out"
"image-rotate-left-symbolic"
"image-rotate-right-symbolic"
"channel-secure-symbolic"
"download"
"smiley"
"tools-check-spelling"
"username-copy"
"system-switch-user"
"bookmark-new"
"bookmark-remove"
"favorite"
"window-new"
"globe"
"visibility"
"home"
"preferences-desktop-notification"
"computer-symbolic"
"gps"
"system-users-symbolic"
"map-flat"
"documentinfo"
"view-list-details"
"go-previous"
"mail-forward-symbolic"
"dialog-warning-symbolic"
"object-rotate-left"
"object-rotate-right"
"add-subtitle"
"security-low"
"security-low-symbolic"
"kde"
"list-remove-symbolic"
"edit-delete"
)
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets KF6::SyntaxHighlighting)
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
endif()
if(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF6::DBusAddons AND NOT WIN32)
target_link_libraries(neochat PUBLIC KF6::DBusAddons)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()
if (TARGET KF6::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()
if (TARGET KUnifiedPush)
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
target_link_libraries(neochat PUBLIC KUnifiedPush)
if (NOT ANDROID)
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
endif()
endif()
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()

178
src/actionshandler.cpp Normal file
View File

@@ -0,0 +1,178 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "actionshandler.h"
#include <Quotient/csapi/joining.h>
#include <Quotient/events/roommemberevent.h>
#include <cmark.h>
#include <KLocalizedString>
#include <QStringBuilder>
#include "models/actionsmodel.h"
#include "neochatconfig.h"
#include "texthandler.h"
using namespace Quotient;
ActionsHandler::ActionsHandler(QObject *parent)
: QObject(parent)
{
}
NeoChatRoom *ActionsHandler::room() const
{
return m_room;
}
void ActionsHandler::setRoom(NeoChatRoom *room)
{
if (m_room == room) {
return;
}
m_room = room;
Q_EMIT roomChanged();
}
void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
{
if (!m_room || !chatBarCache) {
qWarning() << "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.";
return;
}
checkEffects(chatBarCache->text());
if (!chatBarCache->attachmentPath().isEmpty()) {
QUrl url(chatBarCache->attachmentPath());
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
m_room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
chatBarCache->setAttachmentPath({});
chatBarCache->setText({});
return;
}
QString handledText = chatBarCache->text();
handledText = handleMentions(handledText, chatBarCache->mentions());
handleMessage(m_room->mainCache()->text(), handledText, chatBarCache);
}
QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *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 : *mentions) {
if (mention.text.isEmpty() || mention.id.isEmpty()) {
continue;
}
handledText = handledText.replace(mention.cursor.anchor(),
mention.cursor.position() - mention.cursor.anchor(),
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
}
mentions->clear();
return handledText;
}
void ActionsHandler::handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache)
{
Q_ASSERT(m_room);
if (NeoChatConfig::allowQuickEdit()) {
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
auto match = sed.match(text);
if (match.hasMatch()) {
const QString regex = match.captured(1);
const QString replacement = match.captured(2).toHtmlEscaped();
const QString flags = match.captured(3);
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
#if Quotient_VERSION_MINOR > 8
if (event->senderId() == m_room->localMember().id() && event->has<EventContent::TextContent>()) {
#else
if (event->senderId() == m_room->localMember().id() && event->hasTextContent()) {
#endif
QString originalString;
if (event->content()) {
#if Quotient_VERSION_MINOR > 8
originalString = event->get<EventContent::TextContent>()->body;
#else
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
#endif
} else {
originalString = event->plainBody();
}
if (flags == "/g"_ls) {
m_room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
} else {
m_room->postHtmlMessage(handledText,
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
event->msgtype(),
{},
event->id());
}
return;
}
}
}
}
}
auto messageType = RoomMessageEvent::MsgType::Text;
if (handledText.startsWith(QLatin1Char('/'))) {
for (const auto &action : ActionsModel::instance().allActions()) {
if (handledText.indexOf(action.prefix) == 1
&& (handledText.indexOf(" "_ls) == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room, chatBarCache);
if (action.messageType.has_value()) {
messageType = *action.messageType;
}
if (action.messageAction) {
break;
} else {
return;
}
}
}
}
TextHandler textHandler;
textHandler.setData(handledText);
handledText = textHandler.handleSendText();
if (handledText.count("<p>"_ls) == 1 && handledText.count("</p>"_ls) == 1) {
handledText.remove("<p>"_ls);
handledText.remove("</p>"_ls);
}
if (handledText.length() == 0) {
return;
}
m_room->postMessage(text, handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
}
void ActionsHandler::checkEffects(const QString &text)
{
std::optional<QString> effect = std::nullopt;
if (text.contains(QStringLiteral("\u2744"))) {
effect = QLatin1String("snowflake");
} else if (text.contains(QStringLiteral("\u1F386"))) {
effect = QLatin1String("fireworks");
} else if (text.contains(QStringLiteral("\u2F387"))) {
effect = QLatin1String("fireworks");
} else if (text.contains(QStringLiteral("\u1F389"))) {
effect = QLatin1String("confetti");
} else if (text.contains(QStringLiteral("\u1F38A"))) {
effect = QLatin1String("confetti");
}
if (effect.has_value()) {
Q_EMIT showEffect(*effect);
}
}
#include "moc_actionshandler.cpp"

66
src/actionshandler.h Normal file
View File

@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <Quotient/events/roommessageevent.h>
#include "chatbarcache.h"
#include "neochatroom.h"
class NeoChatRoom;
/**
* @class ActionsHandler
*
* This class handles chat messages ready for posting to a room.
*
* Everything that needs to be done to prepare the message for posting in a room
* including:
* - File handling
* - User mentions
* - Quick edits
* - Chat actions
* - Custom emojis
*
* @note A chat action is a message starting with /, resulting in something other
* than a normal message being sent (e.g. /me, /join).
*
* @sa ActionsModel, NeoChatRoom
*/
class ActionsHandler : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The room that messages will be sent to.
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
public:
explicit ActionsHandler(QObject *parent = nullptr);
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
Q_SIGNALS:
void roomChanged();
void showEffect(const QString &effect);
public Q_SLOTS:
/**
* @brief Pre-process text and send message event.
*/
void handleMessageEvent(ChatBarCache *chatBarCache);
private:
QPointer<NeoChatRoom> m_room;
void checkEffects(const QString &text);
QString handleMentions(QString handledText, QList<Mention> *mentions);
void handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache);
};

View File

@@ -1,366 +0,0 @@
# 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 <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
add_library(neochat STATIC
controller.cpp
controller.h
roommanager.cpp
roommanager.h
models/userdirectorylistmodel.cpp
models/userdirectorylistmodel.h
notificationsmanager.cpp
notificationsmanager.h
blurhash.cpp
blurhash.h
blurhashimageprovider.cpp
blurhashimageprovider.h
windowcontroller.cpp
windowcontroller.h
models/serverlistmodel.cpp
models/serverlistmodel.h
models/notificationsmodel.cpp
models/notificationsmodel.h
proxycontroller.cpp
proxycontroller.h
mediamanager.cpp
mediamanager.h
sharehandler.cpp
sharehandler.h
foreigntypes.h
identityserverhelper.cpp
identityserverhelper.h
models/commonroomsmodel.cpp
models/commonroomsmodel.h
texttospeechhelper.h
texttospeechhelper.cpp
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction
)
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME GlobalMenu
)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
QML_FILES
qml/Main.qml
qml/AccountMenu.qml
qml/CollapsedRoomDelegate.qml
qml/RoomPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/NeochatMaximizeComponent.qml
qml/QuickSwitcher.qml
qml/AttachmentPane.qml
qml/QuickFormatBar.qml
qml/UserDetailDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
qml/VerificationMessage.qml
qml/EmojiItem.qml
qml/EmojiRow.qml
qml/EmojiSas.qml
qml/VerificationCanceled.qml
qml/MessageSourceSheet.qml
qml/LocationChooser.qml
qml/InvitationView.qml
qml/AvatarTabButton.qml
qml/OsmLocationPlugin.qml
qml/FullScreenMap.qml
qml/ChooseRoomDialog.qml
qml/QrCodeMaximizeComponent.qml
qml/NotificationsView.qml
qml/ServerComboBox.qml
qml/UserSearchPage.qml
qml/ManualUserDialog.qml
qml/RecommendedSpaceDialog.qml
qml/ShareDialog.qml
qml/UnlockSSSSDialog.qml
qml/QrScannerPage.qml
qml/JoinRoomDialog.qml
qml/ConfirmUrlDialog.qml
qml/AccountSwitchDialog.qml
qml/ConfirmLeaveDialog.qml
qml/CodeMaximizeComponent.qml
qml/EditStateDialog.qml
qml/ConsentDialog.qml
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
qml/AvatarNotification.qml
qml/ReasonDialog.qml
qml/NewPollDialog.qml
qml/UserMenu.qml
DEPENDENCIES
QtCore
QtQuick
IMPORTS
org.kde.neochat.libneochat
org.kde.neochat.rooms
org.kde.neochat.roominfo
org.kde.neochat.messagecontent
org.kde.neochat.timeline
org.kde.neochat.spaces
org.kde.neochat.settings
org.kde.neochat.devtools
org.kde.neochat.login
org.kde.neochat.chatbar
)
if(NOT ANDROID AND NOT WIN32)
qt_target_qml_sources(neochat QML_FILES
qml/ShareAction.qml
qml/GlobalMenu.qml
qml/EditMenu.qml
)
else()
qt_target_qml_sources(neochat QML_FILES
qml/ShareActionStub.qml
qml/GlobalMenuStub.qml
)
endif()
if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
add_executable(neochat-app
main.cpp
)
if(TARGET Qt::WebView)
target_link_libraries(neochat-app PUBLIC Qt::WebView)
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
endif()
target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat-app PRIVATE
neochat
)
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
if(NOT ANDROID)
if (NOT WIN32 AND NOT APPLE)
target_sources(neochat PRIVATE trayicon_sni.cpp trayicon_sni.h)
target_link_libraries(neochat PRIVATE KF6::StatusNotifierItem)
else()
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
endif()
target_link_libraries(neochat PUBLIC KF6::WindowSystem)
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp)
endif()
else()
target_compile_definitions(neochat PUBLIC -DHAVE_X11=0)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PUBLIC
LibNeoChat
Timeline
Settings
Qt::Core
Qt::Quick
Qt::Qml
Qt::Gui
Qt::Multimedia
Qt::Network
Qt::QuickControls2
Qt::TextToSpeech
KF6::I18n
KF6::Kirigami
KF6::Notifications
KF6::ConfigCore
KF6::ConfigGui
KF6::CoreAddons
KF6::SonnetCore
KF6::IconThemes
KF6::ItemModels
QuotientQt6
Login
Rooms
MessageContent
Spaces
)
if (TARGET KF6::Crash)
target_link_libraries(neochat PUBLIC KF6::Crash)
endif()
kconfig_target_kcfg_file(neochat FILE neochatconfig.kcfg CLASS_NAME NeoChatConfig MUTATORS GENERATE_PROPERTIES DEFAULT_VALUE_GETTERS PARENT_IN_CONSTRUCTOR SINGLETON GENERATE_MOC QML_REGISTRATION)
if(NEOCHAT_FLATPAK)
target_compile_definitions(neochat PUBLIC NEOCHAT_FLATPAK)
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
"arrow-down-symbolic"
"arrow-up-symbolic"
"arrow-up-double-symbolic"
"arrow-left-symbolic"
"arrow-right-symbolic"
"checkmark"
"help-about"
"im-user"
"im-invisible-user"
"im-kick-user"
"mail-attachment"
"dialog-cancel"
"preferences-desktop-emoticons"
"preferences-security"
"document-open"
"document-save"
"document-send"
"dialog-close"
"edit-delete-remove"
"code-context"
"document-edit"
"list-user-add"
"list-add-user"
"user-others"
"media-playback-pause"
"media-playback-start"
"media-playback-stop"
"go-previous"
"go-up"
"go-down"
"list-add"
"irc-join-channel"
"settings-configure"
"configure"
"rating"
"rating-unrated"
"search"
"mail-replied-symbolic"
"edit-clear"
"edit-copy"
"gtk-quit"
"compass"
"computer"
"network-connect"
"list-remove-user"
"org.kde.neochat"
"org.kde.neochat.tray"
"preferences-system-users"
"preferences-desktop-theme-global"
"notifications"
"notifications-disabled"
"audio-volume-high"
"audio-volume-muted"
"draw-highlight"
"zoom-in"
"zoom-out"
"image-rotate-left-symbolic"
"image-rotate-right-symbolic"
"channel-secure-symbolic"
"download"
"smiley"
"tools-check-spelling"
"username-copy"
"system-switch-user"
"bookmark-new"
"bookmark-remove"
"favorite"
"window-new"
"globe"
"visibility"
"home"
"preferences-desktop-notification"
"computer-symbolic"
"gps"
"system-users-symbolic"
"map-flat"
"documentinfo"
"view-list-details"
"go-previous"
"mail-forward-symbolic"
"dialog-warning-symbolic"
"object-rotate-left"
"object-rotate-right"
"add-subtitle"
"security-high"
"security-low"
"security-low-symbolic"
"kde"
"list-remove-symbolic"
"edit-delete"
"user-home-symbolic"
"pin-symbolic"
"kt-restore-defaults-symbolic"
"user-symbolic"
"mark-location-symbolic"
${KIRIGAMI_ADDONS_ICONS}
)
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets)
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
endif()
if(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF6::DBusAddons AND NOT WIN32)
target_link_libraries(neochat PUBLIC KF6::DBusAddons)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()
if (TARGET KF6::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()
if (TARGET KUnifiedPush)
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
target_link_libraries(neochat PUBLIC KUnifiedPush)
if (NOT ANDROID)
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
endif()
endif()
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()
if (APPLE)
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.neochat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "NeoChat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${RELEASE_SERVICE_VERSION})
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${RELEASE_SERVICE_VERSION})
endif ()

View File

@@ -1,414 +0,0 @@
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-3.0-only
#include "controller.h"
#include <Quotient/connection.h>
#include <qt6keychain/keychain.h>
#include <KLocalizedString>
#include <QGuiApplication>
#include <QTimer>
#include <signal.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/events/roommemberevent.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "accountmanager.h"
#include "enums/roomsortparameter.h"
#include "mediasizehelper.h"
#include "models/actionsmodel.h"
#include "models/messagemodel.h"
#include "models/pushrulemodel.h"
#include "models/roomlistmodel.h"
#include "models/roomtreemodel.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "proxycontroller.h"
#include "roommanager.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
#elif !defined(Q_OS_ANDROID)
#include "trayicon_sni.h"
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#endif
#endif
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
using namespace Quotient;
static std::function<bool(const Quotient::RoomEvent *)> hiddenEventFilter = [](const RoomEvent *event) -> bool {
if (event->isStateEvent() && !NeoChatConfig::showStateEvent()) {
return true;
}
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(event)) {
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::showLeaveJoinEvent()) {
return true;
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::showRename()) {
return true;
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::showAvatarUpdate()) {
return true;
}
}
return false;
};
Controller::Controller(QObject *parent)
: QObject(parent)
{
Connection::setRoomType<NeoChatRoom>();
Connection::setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption());
connect(NeoChatConfig::self(), &NeoChatConfig::PreferUsingEncryptionChanged, this, [] {
Connection::setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption());
});
NeoChatConnection::setGlobalUrlPreviewDefault(NeoChatConfig::showLinkPreview());
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this] {
NeoChatConnection::setGlobalUrlPreviewDefault(NeoChatConfig::showLinkPreview());
Q_EMIT globalUrlPreviewDefaultChanged();
});
NeoChatConnection::setKeywordPushRuleDefault(static_cast<PushRuleAction::Action>(NeoChatConfig::keywordPushRuleDefault()));
connect(NeoChatConfig::self(), &NeoChatConfig::KeywordPushRuleDefaultChanged, this, [] {
NeoChatConnection::setKeywordPushRuleDefault(static_cast<PushRuleAction::Action>(NeoChatConfig::keywordPushRuleDefault()));
});
ActionsModel::setAllowQuickEdit(NeoChatConfig::allowQuickEdit());
connect(NeoChatConfig::self(), &NeoChatConfig::AllowQuickEditChanged, this, []() {
ActionsModel::setAllowQuickEdit(NeoChatConfig::allowQuickEdit());
});
MessageModel::setHiddenFilter(hiddenEventFilter);
RoomListModel::setHiddenFilter(hiddenEventFilter);
RoomTreeModel::setHiddenFilter(hiddenEventFilter);
NeoChatRoom::setHiddenFilter(hiddenEventFilter);
MediaSizeHelper::setMaxSize(NeoChatConfig::mediaMaxWidth(), NeoChatConfig::mediaMaxHeight());
connect(NeoChatConfig::self(), &NeoChatConfig::MediaMaxWidthChanged, this, []() {
MediaSizeHelper::setMaxSize(NeoChatConfig::mediaMaxWidth(), NeoChatConfig::mediaMaxHeight());
});
connect(NeoChatConfig::self(), &NeoChatConfig::MediaMaxHeightChanged, this, []() {
MediaSizeHelper::setMaxSize(NeoChatConfig::mediaMaxWidth(), NeoChatConfig::mediaMaxHeight());
});
RoomSortParameter::setSortOrder(static_cast<RoomSortOrder::Order>(NeoChatConfig::sortOrder()));
connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, []() {
RoomSortParameter::setSortOrder(static_cast<RoomSortOrder::Order>(NeoChatConfig::sortOrder()));
});
QList<RoomSortParameter::Parameter> configParamList;
const auto intList = NeoChatConfig::customSortOrder();
std::transform(intList.constBegin(), intList.constEnd(), std::back_inserter(configParamList), [](int param) {
return static_cast<RoomSortParameter::Parameter>(param);
});
RoomSortParameter::setCustomSortOrder(configParamList);
connect(NeoChatConfig::self(), &NeoChatConfig::CustomSortOrderChanged, this, []() {
QList<RoomSortParameter::Parameter> configParamList;
const auto intList = NeoChatConfig::customSortOrder();
std::transform(intList.constBegin(), intList.constEnd(), std::back_inserter(configParamList), [](int param) {
return static_cast<RoomSortParameter::Parameter>(param);
});
RoomSortParameter::setCustomSortOrder(configParamList);
});
ProxyController::instance().setApplicationProxy();
#ifndef Q_OS_ANDROID
setQuitOnLastWindowClosed();
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
#endif
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
delete m_trayIcon;
NeoChatConfig::self()->save();
});
#ifndef Q_OS_WINDOWS
const auto unixExitHandler = [](int) -> void {
QCoreApplication::quit();
};
const int quitSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP};
sigset_t blockingMask;
sigemptyset(&blockingMask);
for (const auto sig : quitSignals) {
sigaddset(&blockingMask, sig);
}
struct sigaction sa;
sa.sa_handler = unixExitHandler;
sa.sa_mask = blockingMask;
sa.sa_flags = 0;
for (auto sig : quitSignals) {
sigaction(sig, &sa, nullptr);
}
#endif
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(u"org.kde.neochat"_s);
connect(connector, &KUnifiedPush::Connector::endpointChanged, this, [this](const QString &endpoint) {
if (!m_accountManager) {
return;
}
m_endpoint = endpoint;
for (auto &quotientConnection : m_accountManager->accounts()->accounts()) {
auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection);
connection->setupPushNotifications(endpoint);
}
});
connector->registerClient(
i18nc("The reason for using push notifications, as in: '[Push notifications are used for] Receiving notifications for new messages'",
"Receiving notifications for new messages"));
#endif
}
Controller &Controller::instance()
{
static Controller _instance;
return _instance;
}
void Controller::setAccountManager(AccountManager *manager)
{
if (manager == m_accountManager) {
return;
}
if (m_accountManager) {
m_accountManager->disconnect(this);
}
m_accountManager = manager;
if (m_accountManager) {
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
}
}
void Controller::initConnection(NeoChatConnection *connection)
{
if (!connection) {
return;
}
connect(
connection,
&NeoChatConnection::syncDone,
this,
[this, connection] {
if (!m_endpoint.isEmpty()) {
connection->setupPushNotifications(m_endpoint);
}
},
Qt::SingleShotConnection);
connect(connection, &NeoChatConnection::syncDone, this, [this, connection]() {
m_notificationsManager.handleNotifications(connection);
});
connect(this, &Controller::globalUrlPreviewDefaultChanged, connection, &NeoChatConnection::globalUrlPreviewEnabledChanged);
connect(connection, &NeoChatConnection::roomAboutToBeLeft, &RoomManager::instance(), &RoomManager::roomLeft);
Q_EMIT connectionAdded(connection);
}
void Controller::teardownConnection(NeoChatConnection *connection)
{
if (!connection) {
return;
}
connection->disconnect(this);
Q_EMIT connectionDropped(connection);
}
void Controller::initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection)
{
if (oldConnection) {
oldConnection->disconnect(this);
}
if (newConnection) {
connect(newConnection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
connect(newConnection, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
newConnection->refreshBadgeNotificationCount();
}
Q_EMIT activeConnectionChanged(newConnection);
}
bool Controller::supportSystemTray() const
{
#ifdef Q_OS_ANDROID
return false;
#else
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
return de != u"GNOME"_s && de != u"Pantheon"_s;
#endif
}
void Controller::setQuitOnLastWindowClosed()
{
#ifndef Q_OS_ANDROID
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
m_trayIcon = new TrayIcon(this);
m_trayIcon->show();
} else {
if (m_trayIcon) {
delete m_trayIcon;
m_trayIcon = nullptr;
}
}
#endif
}
NeoChatConnection *Controller::activeConnection() const
{
if (!m_accountManager) {
return nullptr;
}
return m_accountManager->activeConnection();
}
void Controller::setActiveConnection(NeoChatConnection *connection)
{
if (!m_accountManager) {
return;
}
m_accountManager->setActiveConnection(connection);
}
QStringList Controller::accountsLoading() const
{
if (!m_accountManager) {
return {};
}
return m_accountManager->accountsLoading();
}
void Controller::listenForNotifications()
{
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(u"org.kde.neochat"_s);
auto timer = new QTimer();
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
NotificationsManager::postPushNotification(data);
});
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
// Otherwise, messageReceived is never activated, and this daemon could stick around forever.
timer->start(5000);
connector->registerClient(i18n("Receiving push notifications"));
#endif
}
void Controller::clearInvitationNotification(const QString &roomId)
{
m_notificationsManager.clearInvitationNotification(roomId);
}
void Controller::updateBadgeNotificationCount(int count)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
// copied from Telegram desktop
const auto launcherUrl = "application://org.kde.neochat.desktop"_L1;
// Gnome requires that count is a 64bit integer
const qint64 counterSlice = std::min(count, 9999);
QVariantMap dbusUnityProperties;
if (counterSlice > 0) {
dbusUnityProperties["count"_L1] = counterSlice;
dbusUnityProperties["count-visible"_L1] = true;
} else {
dbusUnityProperties["count-visible"_L1] = false;
}
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_L1, "com.canonical.Unity.LauncherEntry"_L1, "Update"_L1);
signal.setArguments({launcherUrl, dbusUnityProperties});
QDBusConnection::sessionBus().send(signal);
#endif // Q_OS_ANDROID
#else
qGuiApp->setBadgeNumber(count);
#endif // QT_VERSION_CHECK(6, 6, 0)
}
bool Controller::isFlatpak() const
{
#ifdef NEOCHAT_FLATPAK
return true;
#else
return false;
#endif
}
AccountRegistry *Controller::accounts()
{
return m_accountManager->accounts();
}
QString Controller::loadFileContent(const QString &path) const
{
QUrl url(path);
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
file.open(QFile::ReadOnly);
return QString::fromLatin1(file.readAll());
}
void Controller::removeConnection(const QString &userId)
{
m_accountManager->dropConnection(userId);
}
void Controller::revertToDefaultConfig()
{
const auto config = NeoChatConfig::self();
config->setDefaults();
config->save();
}
bool Controller::isImageShown(const QString &eventId)
{
return m_shownImages.contains(eventId);
}
void Controller::markImageShown(const QString &eventId)
{
m_shownImages.append(eventId);
}
void Controller::markImageHidden(const QString &eventId)
{
m_shownImages.removeAll(eventId);
}
#include "moc_controller.cpp"

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