Compare commits

..

31 Commits

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

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

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

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

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

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

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

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

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

(cherry picked from commit dc2f11eb2b)
2024-04-23 14:00:57 +02:00
James Graham
09f433be45 Use 0.8.x for libQuotient flatpak
(cherry picked from commit 13e64a9487)
2024-04-23 14:00:44 +02:00
l10n daemon script
b9901a9167 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-04-23 03:20:13 +00:00
l10n daemon script
8b27d99d82 GIT_SILENT made messages (after extraction) 2024-04-23 02:45:40 +00:00
l10n daemon script
6b53c4d7b1 GIT_SILENT Sync po/docbooks with svn 2024-04-22 03:49:36 +00:00
l10n daemon script
bd28a7f66d SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-04-22 03:40:01 +00:00
l10n daemon script
0d1c09696d GIT_SILENT made messages (after extraction) 2024-04-22 03:04:59 +00:00
Albert Astals Cid
aeb4013d26 GIT_SILENT Upgrade release service version to 24.04.80. 2024-04-21 11:39:05 +02:00
278 changed files with 44294 additions and 59176 deletions

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.7", "runtime-version": "6.6",
"sdk": "org.kde.Sdk", "sdk": "org.kde.Sdk",
"command": "neochat", "command": "neochat",
"tags": [ "tags": [

View File

@@ -8,7 +8,7 @@ include:
- /gitlab-templates/android-qt6.yml - /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml - /gitlab-templates/linux-qt6.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/craft-android-qt6-apks.yml - /gitlab-templates/craft-android-qt6-apks.yml
- /gitlab-templates/craft-appimage-qt6.yml - /gitlab-templates/craft-appimage-qt6.yml

View File

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

View File

@@ -8,7 +8,7 @@ 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 "24") set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "07") set(RELEASE_SERVICE_VERSION_MINOR "04")
set(RELEASE_SERVICE_VERSION_MICRO "80") set(RELEASE_SERVICE_VERSION_MICRO "80")
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}")
@@ -60,9 +60,6 @@ set_package_properties(Qt6 PROPERTIES
) )
qt_policy(SET QTP0001 NEW) qt_policy(SET QTP0001 NEW)
if (QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW)
endif ()
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels 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
@@ -105,7 +102,7 @@ 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.8.2) find_package(QuotientQt6 0.7)
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"

View File

@@ -38,8 +38,8 @@ Due to the nature of the Matrix specification development NeoChat also supports
Details where to find stable releases for NeoChat can be found on its [homepage](https://apps.kde.org/neochat). Details where to find stable releases for NeoChat can be found on its [homepage](https://apps.kde.org/neochat).
Nightly builds for Linux and Windows can be downloaded from [cdn.kde.org](https://cdn.kde.org/ci-builds/network/neochat/). Nightly builds for linux and windows can be downloaded from [cdn.kde.org](https://cdn.kde.org/ci-builds/network/neochat/).
Nightly builds for Android are available from [KDE's nightly F-Droid repository](https://community.kde.org/Android/F-Droid). Nightly builds for android are available from [KDE's nightly F-Droid repository](https://community.kde.org/Android/F-Droid).
Nightly Flatpaks are available from [KDE's nightly Flatpak repository](https://userbase.kde.org/Tutorials/Flatpak). Nightly Flatpaks are available from [KDE's nightly Flatpak repository](https://userbase.kde.org/Tutorials/Flatpak).
## Building NeoChat ## Building NeoChat

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@
#include <QObject> #include <QObject>
#include <QTest> #include <QTest>
#include <Quotient/roommember.h>
#include <Quotient/syncdata.h> #include <Quotient/syncdata.h>
#include <qtestcase.h> #include <qtestcase.h>
@@ -51,7 +50,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->relationUser(), room->member(QString())); QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationMessage(), QString()); QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QString()); QCOMPARE(chatBarCache->attachmentPath(), QString());
} }
@@ -65,7 +64,7 @@ void ChatBarCacheTest::noRoom()
// 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->relationUser(), Quotient::RoomMember()); QCOMPARE(chatBarCache->relationUser(), QVariantMap());
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());
@@ -81,7 +80,7 @@ void ChatBarCacheTest::badParent()
// 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->relationUser(), Quotient::RoomMember()); QCOMPARE(chatBarCache->relationUser(), QVariantMap());
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());
@@ -99,7 +98,7 @@ void ChatBarCacheTest::reply()
QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org")); 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->relationUser(), room->member(QLatin1String("@example:example.org"))); QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message")); QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString()); QCOMPARE(chatBarCache->attachmentPath(), QString());
} }
@@ -116,7 +115,7 @@ void ChatBarCacheTest::edit()
QCOMPARE(chatBarCache->replyId(), QString()); QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), true); QCOMPARE(chatBarCache->isEditing(), true);
QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org")); QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org"))); QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message")); QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString()); QCOMPARE(chatBarCache->attachmentPath(), QString());
} }
@@ -133,7 +132,7 @@ void ChatBarCacheTest::attachment()
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->relationUser(), room->member(QString())); QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationMessage(), QString()); QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path")); QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
} }

View File

@@ -25,7 +25,7 @@
"content": { "content": {
"user_ids": [ "user_ids": [
"@alice:matrix.org", "@alice:matrix.org",
"@bob:kde.org" "@bob:example.com"
] ]
}, },
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
@@ -35,7 +35,7 @@
"content": { "content": {
"$153456789:example.org": { "$153456789:example.org": {
"m.read": { "m.read": {
"@alice:example.org": { "@alice:matrix.org": {
"ts": 1436451550453 "ts": 1436451550453
} }
} }
@@ -47,7 +47,7 @@
"content": { "content": {
"$1532735824654:example.org": { "$1532735824654:example.org": {
"m.read": { "m.read": {
"@bob:kde.org": { "@bob:example.com": {
"ts": 1436451550453 "ts": 1436451550453
} }
} }
@@ -67,18 +67,6 @@
}, },
"type": "m.receipt" "type": "m.receipt"
}, },
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tim2:example.com": {
"ts": 1436451550454
}
}
}
},
"type": "m.receipt"
},
{ {
"content": { "content": {
"$1532735824654:example.org": { "$1532735824654:example.org": {
@@ -148,22 +136,6 @@
"age": 1234 "age": 1234
} }
}, },
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Bob",
"membership": "join"
},
"event_id": "$143273582443PhrSn: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": { "content": {
"displayname": "Look\nat\nme\nI\nput\nnewlines\nin\nmy\ndisplay name", "displayname": "Look\nat\nme\nI\nput\nnewlines\nin\nmy\ndisplay name",
@@ -184,7 +156,7 @@
"summary": { "summary": {
"m.heroes": [ "m.heroes": [
"@alice:example.com", "@alice:example.com",
"@bob:kde.org" "@bob:example.com"
], ],
"m.invited_member_count": 0, "m.invited_member_count": 0,
"m.joined_member_count": 2 "m.joined_member_count": 2

View File

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

View File

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

View File

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

View File

@@ -37,14 +37,16 @@
"events": [ "events": [
{ {
"content": { "content": {
"displayname": "Example", "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"membership": "join" "displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
}, },
"event_id": "$143273582443PhrSn:example.org", "event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653, "origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org", "sender": "@example:example.org",
"state_key": "@example:example.org", "state_key": "@alice:example.org",
"type": "m.room.member", "type": "m.room.member",
"unsigned": { "unsigned": {
"age": 1234 "age": 1234

View File

@@ -130,23 +130,7 @@
"origin_server_ts": 1432735824653, "origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org", "sender": "@example:example.org",
"state_key": "@alice:matrix.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": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "bob:example.org",
"state_key": "@bob:example.org",
"type": "m.room.member", "type": "m.room.member",
"unsigned": { "unsigned": {
"age": 1234 "age": 1234

View File

@@ -51,21 +51,6 @@
"unsigned": { "unsigned": {
"age": 1234 "age": 1234
} }
},
{
"content": {
"displayname": "Example",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
} }
] ]
}, },

View File

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

View File

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

View File

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

View File

@@ -56,7 +56,6 @@ private Q_SLOTS:
void genericBody(); void genericBody();
void nullGenericBody(); void nullGenericBody();
void markdownBody(); void markdownBody();
void markdownBodyReply();
void subtitle(); void subtitle();
void nullSubtitle(); void nullSubtitle();
void mediaInfo(); void mediaInfo();
@@ -75,6 +74,8 @@ private Q_SLOTS:
void nullThread(); void nullThread();
void location(); void location();
void nullLocation(); void nullLocation();
void readMarkers();
void nullReadMarkers();
}; };
void EventHandlerTest::initTestCase() void EventHandlerTest::initTestCase()
@@ -99,27 +100,28 @@ void EventHandlerTest::nullEventId()
void EventHandlerTest::author() void EventHandlerTest::author()
{ {
auto event = room->messageEvents().at(0).get(); auto event = room->messageEvents().at(0).get();
auto author = room->member(event->senderId()); auto author = room->user(event->senderId());
EventHandler eventHandler(room, event); EventHandler eventHandler(room, event);
auto eventHandlerAuthor = eventHandler.getAuthor(); auto eventHandlerAuthor = eventHandler.getAuthor();
QCOMPARE(eventHandlerAuthor.isLocalMember(), author.id() == room->localMember().id()); QCOMPARE(eventHandlerAuthor["isLocalUser"_ls], author->id() == room->localUser()->id());
QCOMPARE(eventHandlerAuthor.id(), author.id()); QCOMPARE(eventHandlerAuthor["id"_ls], author->id());
QCOMPARE(eventHandlerAuthor.displayName(), author.displayName()); QCOMPARE(eventHandlerAuthor["displayName"_ls], author->displayname(room));
QCOMPARE(eventHandlerAuthor.avatarUrl(), author.avatarUrl()); QCOMPARE(eventHandlerAuthor["avatarSource"_ls], room->avatarForMember(author));
QCOMPARE(eventHandlerAuthor.avatarMediaId(), author.avatarMediaId()); QCOMPARE(eventHandlerAuthor["avatarMediaId"_ls], author->avatarMediaId(room));
QCOMPARE(eventHandlerAuthor.color(), author.color()); QCOMPARE(eventHandlerAuthor["color"_ls], Utils::getUserColor(author->hueF()));
QCOMPARE(eventHandlerAuthor["object"_ls], QVariant::fromValue(author));
} }
void EventHandlerTest::nullAuthor() void EventHandlerTest::nullAuthor()
{ {
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getAuthor(), RoomMember()); QCOMPARE(emptyHandler.getAuthor(), QVariantMap());
EventHandler noEventHandler(room, nullptr); EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user."); QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user.");
QCOMPARE(noEventHandler.getAuthor(), RoomMember()); QCOMPARE(noEventHandler.getAuthor(), room->getUser(nullptr));
} }
void EventHandlerTest::authorDisplayName() void EventHandlerTest::authorDisplayName()
@@ -299,13 +301,6 @@ void EventHandlerTest::markdownBody()
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message")); QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
} }
void EventHandlerTest::markdownBodyReply()
{
EventHandler eventHandler(room, room->messageEvents().at(5).get());
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("reply"));
}
void EventHandlerTest::subtitle() void EventHandlerTest::subtitle()
{ {
EventHandler eventHandler(room, room->messageEvents().at(0).get()); EventHandler eventHandler(room, room->messageEvents().at(0).get());
@@ -390,30 +385,31 @@ void EventHandlerTest::nullReplyId()
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->user(replyEvent->senderId());
EventHandler eventHandler(room, room->messageEvents().at(5).get()); EventHandler eventHandler(room, room->messageEvents().at(5).get());
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor(); auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
QCOMPARE(eventHandlerReplyAuthor.isLocalMember(), replyAuthor.id() == room->localMember().id()); QCOMPARE(eventHandlerReplyAuthor["isLocalUser"_ls], replyAuthor->id() == room->localUser()->id());
QCOMPARE(eventHandlerReplyAuthor.id(), replyAuthor.id()); QCOMPARE(eventHandlerReplyAuthor["id"_ls], replyAuthor->id());
QCOMPARE(eventHandlerReplyAuthor.displayName(), replyAuthor.displayName()); QCOMPARE(eventHandlerReplyAuthor["displayName"_ls], replyAuthor->displayname(room));
QCOMPARE(eventHandlerReplyAuthor.avatarUrl(), replyAuthor.avatarUrl()); QCOMPARE(eventHandlerReplyAuthor["avatarSource"_ls], room->avatarForMember(replyAuthor));
QCOMPARE(eventHandlerReplyAuthor.avatarMediaId(), replyAuthor.avatarMediaId()); QCOMPARE(eventHandlerReplyAuthor["avatarMediaId"_ls], replyAuthor->avatarMediaId(room));
QCOMPARE(eventHandlerReplyAuthor.color(), replyAuthor.color()); QCOMPARE(eventHandlerReplyAuthor["color"_ls], Utils::getUserColor(replyAuthor->hueF()));
QCOMPARE(eventHandlerReplyAuthor["object"_ls], QVariant::fromValue(replyAuthor));
EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get()); EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), RoomMember()); QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), room->getUser(nullptr));
} }
void EventHandlerTest::nullReplyAuthor() void EventHandlerTest::nullReplyAuthor()
{ {
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReplyAuthor(), RoomMember()); QCOMPARE(emptyHandler.getReplyAuthor(), QVariantMap());
EventHandler noEventHandler(room, nullptr); EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user."); QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user.");
QCOMPARE(noEventHandler.getReplyAuthor(), RoomMember()); QCOMPARE(noEventHandler.getReplyAuthor(), room->getUser(nullptr));
} }
void EventHandlerTest::replyBody() void EventHandlerTest::replyBody()
@@ -519,5 +515,59 @@ void EventHandlerTest::nullLocation()
QCOMPARE(emptyHandler.getLocationAssetType(), QString()); QCOMPARE(emptyHandler.getLocationAssetType(), QString());
} }
void EventHandlerTest::readMarkers()
{
EventHandler eventHandler(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandler.hasReadMarkers(), true);
auto readMarkers = eventHandler.getReadMarkers();
QCOMPARE(readMarkers.size(), 1);
QCOMPARE(readMarkers[0].toMap()["id"_ls], QStringLiteral("@alice:matrix.org"));
QCOMPARE(eventHandler.getNumberExcessReadMarkers(), QString());
QCOMPARE(eventHandler.getReadMarkersString(), QStringLiteral("1 user: @alice:matrix.org"));
EventHandler eventHandler2(room, room->messageEvents().at(2).get());
QCOMPARE(eventHandler2.hasReadMarkers(), true);
readMarkers = eventHandler2.getReadMarkers();
QCOMPARE(readMarkers.size(), 5);
QCOMPARE(eventHandler2.getNumberExcessReadMarkers(), QStringLiteral("+ 1"));
// There are no guarantees on the order of the users it will be different every time so don't match the whole string.
QCOMPARE(eventHandler2.getReadMarkersString().startsWith(QStringLiteral("6 users:")), true);
}
void EventHandlerTest::nullReadMarkers()
{
QTest::ignoreMessage(QtWarningMsg, "hasReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReadMarkers(), QVariantList());
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getNumberExcessReadMarkers(), QString());
QTest::ignoreMessage(QtWarningMsg, "getReadMarkersString called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReadMarkersString(), QString());
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "hasReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReadMarkers(), QVariantList());
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getNumberExcessReadMarkers(), QString());
QTest::ignoreMessage(QtWarningMsg, "getReadMarkersString called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReadMarkersString(), QString());
}
QTEST_MAIN(EventHandlerTest) QTEST_MAIN(EventHandlerTest)
#include "eventhandlertest.moc" #include "eventhandlertest.moc"

View File

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

View File

@@ -52,8 +52,10 @@ void ReactionModelTest::basicReaction()
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QStringLiteral("<span style=\"font-family: 'emoji';\">👍</span>")); QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QStringLiteral("<span style=\"font-family: 'emoji';\">👍</span>"));
QCOMPARE(model.data(model.index(0), ReactionModel::ReactionRole), QStringLiteral("👍")); QCOMPARE(model.data(model.index(0), ReactionModel::ReactionRole), QStringLiteral("👍"));
QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole), QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole),
QStringLiteral("Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>")); QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalMember), false); auto authorList = QVariantList{room->getUser(room->user(QStringLiteral("@alice:matrix.org")))};
QCOMPARE(model.data(model.index(0), ReactionModel::AuthorsRole), authorList);
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalUser), false);
} }
void ReactionModelTest::newReaction() void ReactionModelTest::newReaction()
@@ -63,7 +65,7 @@ void ReactionModelTest::newReaction()
QCOMPARE(model->rowCount(), 1); QCOMPARE(model->rowCount(), 1);
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole), QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
QStringLiteral("Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>")); QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
QSignalSpy spy(model, SIGNAL(modelReset())); QSignalSpy spy(model, SIGNAL(modelReset()));
@@ -72,7 +74,7 @@ void ReactionModelTest::newReaction()
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), QStringLiteral("😆")); 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),
QStringLiteral("Alice Margatroid and Bob reacted with <span style=\"font-family: 'emoji';\">👍</span>")); QStringLiteral("@alice:matrix.org and @bob:example.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
delete model; delete model;
} }

View File

@@ -54,7 +54,6 @@
<summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary> <summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
<summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary> <summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary>
<summary xml:lang="cs">Mluvte se svými přáteli na Matrixu</summary> <summary xml:lang="cs">Mluvte se svými přáteli na Matrixu</summary>
<summary xml:lang="en-GB">Chat with your friends on matrix</summary>
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary> <summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
<summary xml:lang="es">Charle con sus amigos en matrix</summary> <summary xml:lang="es">Charle con sus amigos en matrix</summary>
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary> <summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
@@ -80,25 +79,20 @@
<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="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="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>
<p xml:lang="eu">NeoChat, Matrix sarearen abantaila guztiei probetsua ateratzeko aukera ematen dizun berriketa aplikaizo bat da. Zure familiari, kideei eta lagunei testu mezuak, bideoak eta audio fitxategiak era seguruan bidaltzeko aukera ematen dizu.</p> <p xml:lang="eu">NeoChat, Matrix sarearen abantaila guztiei probetsua ateratzeko aukera ematen dizun berriketa aplikaizo bat da. Zure familiari, kideei eta lagunei testu mezuak, bideoak eta audio fitxategiak era seguruan bidaltzeko aukera ematen dizu.</p>
<p xml:lang="fi">NeoChat on keskustelusovellus, jolla Matrix-verkosta saa täyden hyödyn. Se tarjoaa salatun kanavan lähettää perheelle, työkavereille ja ystäville tekstiviestejä sekä video- ja äänitiedostoja.</p> <p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé denvoyer 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="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="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="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="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="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="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>
@@ -115,7 +109,7 @@
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p> <p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p> <p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p> <p xml:lang="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 plenemente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
<p xml:lang="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>
<p xml:lang="ka">NeoChat მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p> <p xml:lang="ka">NeoChat მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
<p xml:lang="ko">NeoChat은 Matrix 표준을 따르는 프로그램을 목표로 합니다. 현재 안정 버전의 표준에서 제공하는 기능의 대부분을 지원하며, VoIP, 스레드, 일부 종단간 암호화와 같은 기능은 아직 지원하지 않습니다. Matrix 표준은 계속하여 진화 중이기 때문에 일부 기능이 빠져 있을 수도 있지만 장기적으로는 전체 표준을 지원하는 것이 목표입니다.</p> <p xml:lang="ko">NeoChat은 Matrix 표준을 따르는 프로그램을 목표로 합니다. 현재 안정 버전의 표준에서 제공하는 기능의 대부분을 지원하며, VoIP, 스레드, 일부 종단간 암호화와 같은 기능은 아직 지원하지 않습니다. Matrix 표준은 계속하여 진화 중이기 때문에 일부 기능이 빠져 있을 수도 있지만 장기적으로는 전체 표준을 지원하는 것이 목표입니다.</p>
@@ -124,7 +118,6 @@
<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="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</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>
@@ -152,7 +145,6 @@
<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="ru">В силу природы разработки спецификации 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>
@@ -210,11 +202,10 @@
<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="ru">Наборы стикеров — 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">Yapışkan 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="x-test">xxSticker Packs - MSC2545xx</li>
<li xml:lang="zh-TW">貼圖包 - MSC2545</li> <li xml:lang="zh-TW">貼圖包 - MSC2545</li>
@@ -239,7 +230,6 @@
<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="ru">События местоположения — 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>
@@ -285,7 +275,6 @@
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption> <caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
<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="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>
<caption xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</caption> <caption xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</caption>
@@ -302,7 +291,6 @@
<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="ru">Главное окно со списком комнат, чатом и информацией о комнате</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>
@@ -314,14 +302,11 @@
<screenshot type="default"> <screenshot type="default">
<image>https://cdn.kde.org/screenshots/neochat/spaces.png</image> <image>https://cdn.kde.org/screenshots/neochat/spaces.png</image>
<caption>Discover new communities with Matrix Spaces</caption> <caption>Discover new communities with Matrix Spaces</caption>
<caption xml:lang="ar">اكتشف مجتمعات جديدة مع فضاءات ماتركس</caption>
<caption xml:lang="ca">Descobriu comunitats noves amb els espais de Matrix</caption> <caption xml:lang="ca">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="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>
<caption xml:lang="eu">Ezagutu komunitate berriak Matrixeko Tokiak erabiliz</caption> <caption xml:lang="eu">Ezagutu komunitate berriak Matrixeko Tokiak erabiliz</caption>
<caption xml:lang="fi">Löydä uusia yhteisöjä Matrix Spacesillä</caption>
<caption xml:lang="fr">Découvrez de nouvelles communautés avec les espaces sous Matrix</caption> <caption xml:lang="fr">Découvrez de nouvelles communautés avec les espaces sous Matrix</caption>
<caption xml:lang="hu">Fedezzen fel új közösségeket a Matrix Terek segítségével</caption> <caption xml:lang="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>
@@ -329,10 +314,8 @@
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption> <caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</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="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="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="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="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
@@ -351,7 +334,6 @@
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption> <caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
<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="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>
<caption xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</caption> <caption xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</caption>
@@ -368,7 +350,6 @@
<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="ru">Главное окно со списком комнат, чатом и информацией о комнате</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>
@@ -384,7 +365,6 @@
<caption xml:lang="ca">Pantalla d'inici de sessió</caption> <caption xml:lang="ca">Pantalla d'inici de sessió</caption>
<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="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>
<caption xml:lang="eu">Saio-hasteko pantaila</caption> <caption xml:lang="eu">Saio-hasteko pantaila</caption>
@@ -401,7 +381,6 @@
<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="ru">Окно входа</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>
@@ -415,9 +394,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="24.05.2" date="2024-07-04"/>
<release version="24.05.1" date="2024-06-13"/>
<release version="24.05.0" date="2024-05-23"/>
<release version="24.02.2" date="2024-04-11"/> <release version="24.02.2" date="2024-04-11"/>
<release version="24.02.1" date="2024-03-21"/> <release version="24.02.1" date="2024-03-21"/>
<release version="24.02.0" date="2024-02-28"> <release version="24.02.0" date="2024-02-28">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@ add_library(neochat STATIC
models/customemojimodel.h models/customemojimodel.h
clipboard.cpp clipboard.cpp
clipboard.h clipboard.h
matriximageprovider.cpp
matriximageprovider.h
models/messageeventmodel.cpp models/messageeventmodel.cpp
models/messageeventmodel.h models/messageeventmodel.h
models/messagefiltermodel.cpp models/messagefiltermodel.cpp
@@ -134,8 +136,6 @@ add_library(neochat STATIC
jobs/neochatdeletedevicejob.h jobs/neochatdeletedevicejob.h
jobs/neochatchangepasswordjob.cpp jobs/neochatchangepasswordjob.cpp
jobs/neochatchangepasswordjob.h jobs/neochatchangepasswordjob.h
jobs/neochatgetcommonroomsjob.cpp
jobs/neochatgetcommonroomsjob.h
mediasizehelper.cpp mediasizehelper.cpp
mediasizehelper.h mediasizehelper.h
eventhandler.cpp eventhandler.cpp
@@ -158,6 +158,7 @@ add_library(neochat STATIC
models/linemodel.cpp models/linemodel.cpp
models/linemodel.h models/linemodel.h
events/locationbeaconevent.h events/locationbeaconevent.h
events/serveraclevent.h
events/widgetevent.h events/widgetevent.h
enums/messagecomponenttype.h enums/messagecomponenttype.h
models/messagecontentmodel.cpp models/messagecontentmodel.cpp
@@ -176,27 +177,13 @@ add_library(neochat STATIC
foreigntypes.h foreigntypes.h
models/threepidmodel.cpp models/threepidmodel.cpp
models/threepidmodel.h 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
) )
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE QT_QML_SINGLETON_TYPE TRUE
) )
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
QML_FILES QML_FILES
qml/Main.qml qml/Main.qml
@@ -220,10 +207,16 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/TypingPane.qml qml/TypingPane.qml
qml/QuickSwitcher.qml qml/QuickSwitcher.qml
qml/HoverActions.qml qml/HoverActions.qml
qml/ChatBar.qml
qml/AttachmentPane.qml qml/AttachmentPane.qml
qml/ReplyPane.qml
qml/CompletionMenu.qml
qml/PieProgressBar.qml
qml/QuickFormatBar.qml qml/QuickFormatBar.qml
qml/EmojiPicker.qml
qml/UserDetailDialog.qml qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml qml/CreateRoomDialog.qml
qml/EmojiDialog.qml
qml/OpenFileDialog.qml qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml qml/ConfirmLogoutDialog.qml
@@ -243,6 +236,9 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/ConfirmEncryptionDialog.qml qml/ConfirmEncryptionDialog.qml
qml/RemoveSheet.qml qml/RemoveSheet.qml
qml/BanSheet.qml qml/BanSheet.qml
qml/EmojiTonesPicker.qml
qml/EmojiDelegate.qml
qml/EmojiGrid.qml
qml/RoomSearchPage.qml qml/RoomSearchPage.qml
qml/LocationChooser.qml qml/LocationChooser.qml
qml/TimelineView.qml qml/TimelineView.qml
@@ -266,6 +262,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/SelectParentDialog.qml qml/SelectParentDialog.qml
qml/QrCodeMaximizeComponent.qml qml/QrCodeMaximizeComponent.qml
qml/SelectSpacesDialog.qml qml/SelectSpacesDialog.qml
qml/AttachDialog.qml
qml/NotificationsView.qml qml/NotificationsView.qml
qml/SearchPage.qml qml/SearchPage.qml
qml/ServerComboBox.qml qml/ServerComboBox.qml
@@ -283,25 +280,12 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/ConfirmLeaveDialog.qml qml/ConfirmLeaveDialog.qml
qml/CodeMaximizeComponent.qml qml/CodeMaximizeComponent.qml
qml/EditStateDialog.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(timeline)
add_subdirectory(devtools) add_subdirectory(devtools)
add_subdirectory(login) add_subdirectory(login)
add_subdirectory(chatbar)
if(UNIX) if(UNIX)
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml) qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
@@ -384,18 +368,16 @@ endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE) if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER) target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1) target_compile_definitions(neochat PUBLIC -DHAVE_X11)
target_sources(neochat PRIVATE runner.cpp) target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush) if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp) target_sources(neochat PRIVATE fakerunner.cpp)
endif() endif()
else()
target_compile_definitions(neochat PUBLIC -DHAVE_X11=0)
endif() endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums) target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin chatbarplugin) target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin)
target_link_libraries(neochat PUBLIC target_link_libraries(neochat PUBLIC
Qt::Core Qt::Core
Qt::Quick Qt::Quick

View File

@@ -14,6 +14,7 @@
#include "models/actionsmodel.h" #include "models/actionsmodel.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "texthandler.h" #include "texthandler.h"
#include "utils.h"
using namespace Quotient; using namespace Quotient;
@@ -91,7 +92,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) { for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) { if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
if (event->senderId() == m_room->localMember().id() && event->hasTextContent()) { if (event->senderId() == m_room->localUser()->id() && event->hasTextContent()) {
QString originalString; QString originalString;
if (event->content()) { if (event->content()) {
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body; originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
@@ -145,6 +146,26 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
return; return;
} }
// We want to add back the <mx-reply> if it's in the original message but not in the edit, to preserve the reply.
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
if (event->senderId() == m_room->localUser()->id() && event->hasTextContent()) {
QString originalString;
if (event->content()) {
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
} else {
originalString = event->plainBody();
}
const QRegularExpression exp(TextRegex::removeRichReply);
const auto match = exp.match(originalString);
if (match.hasCaptured(0) && !handledText.contains(TextRegex::removeRichReply)) {
handledText.prepend(match.captured(0));
}
}
}
}
m_room->postMessage(text, handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId()); m_room->postMessage(text, handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
} }

View File

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

View File

@@ -3,8 +3,6 @@
#include "chatbarcache.h" #include "chatbarcache.h"
#include <Quotient/roommember.h>
#include "chatdocumenthandler.h" #include "chatdocumenthandler.h"
#include "eventhandler.h" #include "eventhandler.h"
#include "neochatroom.h" #include "neochatroom.h"
@@ -86,7 +84,7 @@ void ChatBarCache::setEditId(const QString &editId)
Q_EMIT attachmentPathChanged(); Q_EMIT attachmentPathChanged();
} }
Quotient::RoomMember ChatBarCache::relationUser() const QVariantMap ChatBarCache::relationUser() const
{ {
if (parent() == nullptr) { if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."; qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
@@ -98,9 +96,9 @@ Quotient::RoomMember ChatBarCache::relationUser() const
return {}; return {};
} }
if (m_relationId.isEmpty()) { if (m_relationId.isEmpty()) {
return room->member(QString()); return room->getUser(nullptr);
} }
return room->member((*room->findInTimeline(m_relationId))->senderId()); return room->getUser(room->user((*room->findInTimeline(m_relationId))->senderId()));
} }
QString ChatBarCache::relationMessage() const QString ChatBarCache::relationMessage() const

View File

@@ -10,12 +10,6 @@
class ChatDocumentHandler; class ChatDocumentHandler;
namespace Quotient
{
class RoomMember;
}
/** /**
* @brief Defines a user mention in the current chat or edit text. * @brief Defines a user mention in the current chat or edit text.
*/ */
@@ -94,13 +88,26 @@ class ChatBarCache : public QObject
Q_PROPERTY(QString editId READ editId WRITE setEditId NOTIFY relationIdChanged) Q_PROPERTY(QString editId READ editId WRITE setEditId NOTIFY relationIdChanged)
/** /**
* @brief Get the RoomMember object for the message being replied to. * @brief Get the user for the message being replied to.
* *
* Returns an empty RoomMember if not replying to a message. * This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
* *
* @sa Quotient::RoomMember * Returns an empty user if not replying to a message.
*
* The user QVariantMap has the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User
*/ */
Q_PROPERTY(Quotient::RoomMember relationUser READ relationUser NOTIFY relationIdChanged) Q_PROPERTY(QVariantMap relationUser READ relationUser NOTIFY relationIdChanged)
/** /**
* @brief The content of the related message. * @brief The content of the related message.
@@ -154,7 +161,7 @@ public:
QString editId() const; QString editId() const;
void setEditId(const QString &editId); void setEditId(const QString &editId);
Quotient::RoomMember relationUser() const; QVariantMap relationUser() const;
QString relationMessage() const; QString relationMessage() const;

View File

@@ -9,20 +9,27 @@
#include <KLocalizedString> #include <KLocalizedString>
#include <QGuiApplication> #include <QGuiApplication>
#include <QNetworkProxy>
#include <QQuickTextDocument>
#include <QQuickWindow>
#include <QStandardPaths>
#include <QStringBuilder>
#include <QTimer> #include <QTimer>
#include <signal.h> #include <signal.h>
#include <Quotient/accountregistry.h> #include <Quotient/accountregistry.h>
#include <Quotient/csapi/logout.h>
#include <Quotient/csapi/notifications.h> #include <Quotient/csapi/notifications.h>
#include <Quotient/eventstats.h>
#include <Quotient/qt_connection_util.h> #include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatconnection.h" #include "neochatconnection.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "notificationsmanager.h" #include "notificationsmanager.h"
#include "proxycontroller.h" #include "proxycontroller.h"
#include "roommanager.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC) #if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h" #include "trayicon.h"
@@ -38,10 +45,6 @@
#endif #endif
#endif #endif
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
bool testMode = false; bool testMode = false;
using namespace Quotient; using namespace Quotient;
@@ -103,16 +106,14 @@ Controller::Controller(QObject *parent)
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() { connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
if (m_accountRegistry.size() > oldAccountCount) { if (m_accountRegistry.size() > oldAccountCount) {
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]); auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
connect( connect(connection, &NeoChatConnection::syncDone, this, [connection]() {
connection, NotificationsManager::instance().handleNotifications(connection);
&NeoChatConnection::syncDone, });
this, connectSingleShot(connection, &NeoChatConnection::syncDone, this, [this, connection] {
[this, connection] { if (!m_endpoint.isEmpty()) {
if (!m_endpoint.isEmpty()) { connection->setupPushNotifications(m_endpoint);
connection->setupPushNotifications(m_endpoint); }
} });
},
Qt::SingleShotConnection);
} }
oldAccountCount = m_accountRegistry.size(); oldAccountCount = m_accountRegistry.size();
}); });
@@ -188,7 +189,7 @@ void Controller::invokeLogin()
m_accountsLoading += accountId; m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged(); Q_EMIT accountsLoadingChanged();
if (!account.homeserver().isEmpty()) { if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId()); auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) { connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId}; AccountSettings account{accountId};
QString accessToken; QString accessToken;
@@ -216,11 +217,11 @@ void Controller::invokeLogin()
} }
} }
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QString &userId) QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
{ {
qDebug() << "Reading access token from the keychain for" << userId; qDebug() << "Reading access token from the keychain for" << account.userId();
auto job = new QKeychain::ReadPasswordJob(qAppName(), this); auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
job->setKey(userId); job->setKey(account.userId());
// Handling of errors // Handling of errors
connect(job, &QKeychain::Job::finished, this, [this, job]() { connect(job, &QKeychain::Job::finished, this, [this, job]() {
@@ -251,12 +252,12 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QStrin
return job; return job;
} }
bool Controller::saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken) bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
{ {
qDebug() << "Save the access token to the keychain for " << userId; qDebug() << "Save the access token to the keychain for " << account.userId();
QKeychain::WritePasswordJob job(qAppName()); QKeychain::WritePasswordJob job(qAppName());
job.setAutoDelete(false); job.setAutoDelete(false);
job.setKey(userId); job.setKey(account.userId());
job.setBinaryData(accessToken); job.setBinaryData(accessToken);
QEventLoop loop; QEventLoop loop;
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
@@ -392,6 +393,8 @@ QString Controller::loadFileContent(const QString &path) const
return QString::fromLatin1(file.readAll()); return QString::fromLatin1(file.readAll());
} }
#include "moc_controller.cpp"
void Controller::setTestMode(bool test) void Controller::setTestMode(bool test)
{ {
testMode = test; testMode = test;
@@ -407,13 +410,11 @@ void Controller::removeConnection(const QString &userId)
} }
} }
bool Controller::csSupported() const bool Controller::ssssSupported() const
{ {
#if Quotient_VERSION_MINOR > 9 #if __has_include("Quotient/e2ee/sssshandler.h")
return true; return true;
#else #else
return false; return false;
#endif #endif
} }
#include "moc_controller.cpp"

View File

@@ -5,9 +5,15 @@
#include <QObject> #include <QObject>
#include <QQmlEngine> #include <QQmlEngine>
#include <QQuickItem>
#include "neochatconnection.h" #include "neochatconnection.h"
#include <Quotient/accountregistry.h> #include <Quotient/accountregistry.h>
#include <Quotient/settings.h>
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
class TrayIcon; class TrayIcon;
class QQuickTextDocument; class QQuickTextDocument;
@@ -50,19 +56,9 @@ class Controller : public QObject
Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged) Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
Q_PROPERTY(bool csSupported READ csSupported CONSTANT) Q_PROPERTY(bool ssssSupported READ ssssSupported CONSTANT)
public: public:
/**
* @brief Define the types on inline messages that can be shown.
*/
enum MessageType {
Positive, /**< Positive message, typically green. */
Info, /**< Info message, typically highlight color. */
Error, /**< Error message, typically red. */
};
Q_ENUM(MessageType)
static Controller &instance(); static Controller &instance();
static Controller *create(QQmlEngine *engine, QJSEngine *) static Controller *create(QQmlEngine *engine, QJSEngine *)
{ {
@@ -86,7 +82,7 @@ public:
/** /**
* @brief Save an access token to the keychain for the given account. * @brief Save an access token to the keychain for the given account.
*/ */
bool saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken); bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
[[nodiscard]] bool supportSystemTray() const; [[nodiscard]] bool supportSystemTray() const;
@@ -106,7 +102,7 @@ public:
Q_INVOKABLE void removeConnection(const QString &userId); Q_INVOKABLE void removeConnection(const QString &userId);
bool csSupported() const; bool ssssSupported() const;
private: private:
explicit Controller(QObject *parent = nullptr); explicit Controller(QObject *parent = nullptr);
@@ -114,7 +110,7 @@ private:
QPointer<NeoChatConnection> m_connection; QPointer<NeoChatConnection> m_connection;
TrayIcon *m_trayIcon = nullptr; TrayIcon *m_trayIcon = nullptr;
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const QString &account); QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
void loadSettings(); void loadSettings();
void saveSettings() const; void saveSettings() const;
@@ -135,5 +131,4 @@ Q_SIGNALS:
void connectionDropped(NeoChatConnection *connection); void connectionDropped(NeoChatConnection *connection);
void activeConnectionChanged(NeoChatConnection *connection); void activeConnectionChanged(NeoChatConnection *connection);
void accountsLoadingChanged(); void accountsLoadingChanged();
void showMessage(MessageType messageType, const QString &message);
}; };

View File

@@ -2,7 +2,7 @@
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
qt_add_library(devtools STATIC) qt_add_library(devtools STATIC)
ecm_add_qml_module(devtools GENERATE_PLUGIN_SOURCE qt_add_qml_module(devtools
URI org.kde.neochat.devtools URI org.kde.neochat.devtools
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/devtools OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/devtools
QML_FILES QML_FILES

View File

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

View File

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

View File

@@ -33,7 +33,6 @@ public:
* a room message. * a room message.
*/ */
enum Type { enum Type {
Author, /**< The message sender and time. */
Text, /**< A text message. */ Text, /**< A text message. */
Image, /**< A message that is an image. */ Image, /**< A message that is an image. */
Audio, /**< A message that is an audio recording. */ Audio, /**< A message that is an audio recording. */
@@ -48,11 +47,11 @@ public:
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */ LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
Encrypted, /**< An encrypted message that cannot be decrypted. */ Encrypted, /**< An encrypted message that cannot be decrypted. */
Reply, /**< A component to show a replied-to message. */ Reply, /**< A component to show a replied-to message. */
ReplyLoad, /**< A loading dialog for a reply. */
LinkPreview, /**< A preview of a URL in the message. */ LinkPreview, /**< A preview of a URL in the message. */
LinkPreviewLoad, /**< A loading dialog for a link preview. */ LinkPreviewLoad, /**< A loading dialog for a link preview. */
Edit, /**< A text edit for editing a message. */ Edit, /**< A text edit for editing a message. */
Verification, /**< A user verification session start message. */ Verification, /**< A user verification session start message. */
Loading, /**< The component is loading. */
Other, /**< Anything that cannot be classified as another type. */ Other, /**< Anything that cannot be classified as another type. */
}; };
Q_ENUM(Type); Q_ENUM(Type);

View File

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

View File

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

View File

@@ -19,11 +19,11 @@
#include <Quotient/events/simplestateevents.h> #include <Quotient/events/simplestateevents.h>
#include <Quotient/events/stickerevent.h> #include <Quotient/events/stickerevent.h>
#include <Quotient/quotient_common.h> #include <Quotient/quotient_common.h>
#include <Quotient/roommember.h>
#include "eventhandler_logging.h" #include "eventhandler_logging.h"
#include "events/locationbeaconevent.h" #include "events/locationbeaconevent.h"
#include "events/pollevent.h" #include "events/pollevent.h"
#include "events/serveraclevent.h"
#include "events/widgetevent.h" #include "events/widgetevent.h"
#include "linkpreviewer.h" #include "linkpreviewer.h"
#include "messagecomponenttype.h" #include "messagecomponenttype.h"
@@ -61,18 +61,20 @@ MessageComponentType::Type EventHandler::messageComponentType() const
return MessageComponentType::typeForEvent(*m_event); return MessageComponentType::typeForEvent(*m_event);
} }
Quotient::RoomMember EventHandler::getAuthor(bool isPending) const QVariantMap EventHandler::getAuthor(bool isPending) const
{ {
if (m_room == nullptr) { if (m_room == nullptr) {
qCWarning(EventHandling) << "getAuthor called with m_room set to nullptr."; qCWarning(EventHandling) << "getAuthor called with m_room set to nullptr.";
return {}; return {};
} }
// If we have a room we can return an empty user by handing nullptr to m_room->getUser.
if (m_event == nullptr) { if (m_event == nullptr) {
qCWarning(EventHandling) << "getAuthor called with m_event set to nullptr. Returning empty user."; qCWarning(EventHandling) << "getAuthor called with m_event set to nullptr. Returning empty user.";
return {}; return m_room->getUser(nullptr);
} }
return isPending ? m_room->localMember() : m_room->member(m_event->senderId()); const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId());
return m_room->getUser(author);
} }
QString EventHandler::getAuthorDisplayName(bool isPending) const QString EventHandler::getAuthorDisplayName(bool isPending) const
@@ -94,8 +96,8 @@ QString EventHandler::getAuthorDisplayName(bool isPending) const
} }
return previousDisplayName; return previousDisplayName;
} else { } else {
const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId()); const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId());
return author.htmlSafeDisplayName(); return m_room->htmlSafeMemberName(author->id());
} }
} }
@@ -110,8 +112,8 @@ QString EventHandler::singleLineAuthorDisplayname(bool isPending) const
return {}; return {};
} }
const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId()); const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId());
auto displayName = author.displayName(); auto displayName = m_room->safeMemberName(author->id());
displayName.replace(QStringLiteral("<br>\n"), QStringLiteral(" ")); displayName.replace(QStringLiteral("<br>\n"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br>"), QStringLiteral(" ")); displayName.replace(QStringLiteral("<br>"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br />\n"), QStringLiteral(" ")); displayName.replace(QStringLiteral("<br />\n"), QStringLiteral(" "));
@@ -218,7 +220,7 @@ bool EventHandler::isHidden()
} }
} }
if (m_room->connection()->isIgnored(m_event->senderId())) { if (m_room->connection()->isIgnored(m_room->user(m_event->senderId()))) {
return true; return true;
} }
@@ -241,21 +243,19 @@ Qt::TextFormat EventHandler::messageBodyInputFormat(const Quotient::RoomMessageE
QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event) QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
{ {
QString body;
if (event.hasFileContent()) { if (event.hasFileContent()) {
// if filename is given or body is equal to filename, auto fileCaption = event.content()->fileInfo()->originalName;
// then body is a caption if (fileCaption.isEmpty()) {
QString filename = event.content()->fileInfo()->originalName; fileCaption = event.plainBody();
QString body = event.plainBody(); } else if (event.content()->fileInfo()->originalName != event.plainBody()) {
if (filename.isEmpty() || filename == body) { fileCaption = event.plainBody() + " | "_ls + fileCaption;
return QString();
} }
return body; return fileCaption;
} }
QString body;
if (event.hasTextContent() && event.content()) { if (event.hasTextContent() && event.content()) {
body = static_cast<const EventContent::TextContent *>(event.content())->body; body = static_cast<const MessageEventContent::TextContent *>(event.content())->body;
} else { } else {
body = event.plainBody(); body = event.plainBody();
} }
@@ -293,10 +293,7 @@ QString EventHandler::getMarkdownBody() const
} }
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event); const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
return roomMessageEvent->plainBody();
QString plainBody = roomMessageEvent->plainBody();
plainBody.remove(TextRegex::removeReply);
return plainBody;
} }
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
@@ -318,19 +315,15 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
}, },
[this, prettyPrint](const RoomMemberEvent &e) { [this, prettyPrint](const RoomMemberEvent &e) {
// FIXME: Rewind to the name that was at the time of this event // FIXME: Rewind to the name that was at the time of this event
auto subjectName = m_room->member(e.userId()).htmlSafeDisplayName(); auto subjectName = m_room->htmlSafeMemberName(e.userId());
if (e.membership() == Membership::Leave) { if (e.membership() == Membership::Leave) {
if (e.prevContent() && e.prevContent()->displayName) { if (e.prevContent() && e.prevContent()->displayName) {
subjectName = sanitized(*e.prevContent()->displayName); subjectName = sanitized(*e.prevContent()->displayName).toHtmlEscaped();
if (prettyPrint) {
subjectName = subjectName.toHtmlEscaped();
}
} }
} }
if (prettyPrint) { if (prettyPrint) {
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>") subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\">%2</a>").arg(e.userId(), subjectName);
.arg(e.userId(), m_room->member(e.userId()).color().name(), subjectName);
} }
// The below code assumes senderName output in AuthorRole // The below code assumes senderName output in AuthorRole
@@ -444,7 +437,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
[](const LocationBeaconEvent &e) { [](const LocationBeaconEvent &e) {
return e.contentJson()["description"_ls].toString(); return e.contentJson()["description"_ls].toString();
}, },
[](const RoomServerAclEvent &) { [](const ServerAclEvent &) {
return i18n("changed the server access control lists for this room"); return i18n("changed the server access control lists for this room");
}, },
[](const WidgetEvent &e) { [](const WidgetEvent &e) {
@@ -483,7 +476,7 @@ QString EventHandler::getMessageBody(const RoomMessageEvent &event, Qt::TextForm
QString body; QString body;
if (event.hasTextContent() && event.content()) { if (event.hasTextContent() && event.content()) {
body = static_cast<const EventContent::TextContent *>(event.content())->body; body = static_cast<const MessageEventContent::TextContent *>(event.content())->body;
} else { } else {
body = event.plainBody(); body = event.plainBody();
} }
@@ -613,7 +606,7 @@ QString EventHandler::getGenericBody() const
[](const LocationBeaconEvent &) { [](const LocationBeaconEvent &) {
return i18n("sent a live location beacon"); return i18n("sent a live location beacon");
}, },
[](const RoomServerAclEvent &) { [](const ServerAclEvent &) {
return i18n("changed the server access control lists for this room"); return i18n("changed the server access control lists for this room");
}, },
[](const WidgetEvent &e) { [](const WidgetEvent &e) {
@@ -662,30 +655,23 @@ QVariantMap EventHandler::getMediaInfoForEvent(const Quotient::RoomEvent *event)
QString eventId = event->id(); QString eventId = event->id();
// Get the file info for the event. // Get the file info for the event.
const EventContent::FileInfo *fileInfo;
bool isSticker = false;
if (event->is<RoomMessageEvent>()) { if (event->is<RoomMessageEvent>()) {
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event); auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
if (!roomMessageEvent->hasFileContent()) { if (!roomMessageEvent->hasFileContent()) {
return {}; return {};
} }
const EventContent::FileInfo *fileInfo;
fileInfo = roomMessageEvent->content()->fileInfo(); fileInfo = roomMessageEvent->content()->fileInfo();
QVariantMap mediaInfo = getMediaInfoFromFileInfo(fileInfo, eventId, false, false);
// if filename isn't specifically given, it is in body
// https://spec.matrix.org/latest/client-server-api/#mfile
mediaInfo["filename"_ls] = (fileInfo->originalName.isEmpty()) ? roomMessageEvent->plainBody() : fileInfo->originalName;
return mediaInfo;
} else if (event->is<StickerEvent>()) { } else if (event->is<StickerEvent>()) {
const EventContent::FileInfo *fileInfo;
auto stickerEvent = eventCast<const StickerEvent>(event); auto stickerEvent = eventCast<const StickerEvent>(event);
fileInfo = &stickerEvent->image(); fileInfo = &stickerEvent->image();
isSticker = true;
return getMediaInfoFromFileInfo(fileInfo, eventId, false, true);
} else { } else {
return {}; return {};
} }
return getMediaInfoFromFileInfo(fileInfo, eventId, false, isSticker);
} }
QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail, bool isSticker) const QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail, bool isSticker) const
@@ -811,21 +797,25 @@ MessageComponentType::Type EventHandler::replyMessageComponentType() const
return MessageComponentType::typeForEvent(*replyEvent); return MessageComponentType::typeForEvent(*replyEvent);
} }
Quotient::RoomMember EventHandler::getReplyAuthor() const QVariantMap EventHandler::getReplyAuthor() const
{ {
if (m_room == nullptr) { if (m_room == nullptr) {
qCWarning(EventHandling) << "getReplyAuthor called with m_room set to nullptr."; qCWarning(EventHandling) << "getReplyAuthor called with m_room set to nullptr.";
return {}; return {};
} }
// If we have a room we can return an empty user by handing nullptr to m_room->getUser.
if (m_event == nullptr) { if (m_event == nullptr) {
qCWarning(EventHandling) << "getReplyAuthor called with m_event set to nullptr. Returning empty user."; qCWarning(EventHandling) << "getReplyAuthor called with m_event set to nullptr. Returning empty user.";
return {}; return m_room->getUser(nullptr);
} }
if (auto replyPtr = m_room->getReplyForEvent(*m_event)) { auto replyPtr = m_room->getReplyForEvent(*m_event);
return m_room->member(replyPtr->senderId());
if (replyPtr) {
auto replyUser = m_room->user(replyPtr->senderId());
return m_room->getUser(replyUser);
} else { } else {
return m_room->member(QString()); return m_room->getUser(nullptr);
} }
} }
@@ -961,4 +951,97 @@ QString EventHandler::getLocationAssetType() const
return assetType; return assetType;
} }
bool EventHandler::hasReadMarkers() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "hasReadMarkers called with m_room set to nullptr.";
return false;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "hasReadMarkers called with m_event set to nullptr.";
return false;
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id());
return userIds.size() > 0;
}
QVariantList EventHandler::getReadMarkers(int maxMarkers) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReadMarkers called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReadMarkers called with m_event set to nullptr.";
return {};
}
auto userIds_temp = m_room->userIdsAtEvent(m_event->id());
userIds_temp.remove(m_room->localUser()->id());
auto userIds = userIds_temp.values();
if (userIds.count() > maxMarkers) {
userIds = userIds.mid(0, maxMarkers);
}
QVariantList users;
users.reserve(userIds.size());
for (const auto &userId : userIds) {
auto user = m_room->user(userId);
users += m_room->getUser(user);
}
return users;
}
QString EventHandler::getNumberExcessReadMarkers(int maxMarkers) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getNumberExcessReadMarkers called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getNumberExcessReadMarkers called with m_event set to nullptr.";
return {};
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id());
if (userIds.count() > maxMarkers) {
return QStringLiteral("+ ") + QString::number(userIds.count() - maxMarkers);
} else {
return QString();
}
}
QString EventHandler::getReadMarkersString() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReadMarkersString called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReadMarkersString called with m_event set to nullptr.";
return {};
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id());
/**
* The string ends up in the form
* "x users: user1DisplayName, user2DisplayName, etc."
*/
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
for (const auto &userId : userIds) {
auto user = m_room->user(userId);
readMarkersString += user->displayname(m_room) + i18nc("list separator", ", ");
}
readMarkersString.chop(2);
return readMarkersString;
}
#include "moc_eventhandler.cpp" #include "moc_eventhandler.cpp"

View File

@@ -13,11 +13,6 @@
#include "enums/messagecomponenttype.h" #include "enums/messagecomponenttype.h"
namespace Quotient
{
class RoomMember;
}
class LinkPreviewer; class LinkPreviewer;
class NeoChatRoom; class NeoChatRoom;
class ReactionModel; class ReactionModel;
@@ -56,17 +51,30 @@ public:
/** /**
* @brief Get the author of the event in context of the room. * @brief Get the author of the event in context of the room.
* *
* An empty Quotient::RoomMember will be returned if the EventHandler hasn't had * This is different to getting a Quotient::User object
* the room or event initialised. * as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* uses the room context and outputs the result as QVariantMap.
*
* An empty QVariantMap will be returned if the EventHandler hasn't had the room
* intialised. An empty user (i.e. a QVariantMap with all the correct keys
* but empty values) will be returned if the room has been set but not an event.
* *
* @param isPending if the event is pending, i.e. has not been confirmed by * @param isPending if the event is pending, i.e. has not been confirmed by
* the server. * the server.
* *
* @return a Quotient::RoomMember object for the user. * @return a QVariantMap for the user with the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
* *
* @sa Quotient::RoomMember * @sa Quotient::User
*/ */
Quotient::RoomMember getAuthor(bool isPending = false) const; QVariantMap getAuthor(bool isPending = false) const;
/** /**
* @brief Get the display name of the event author. * @brief Get the display name of the event author.
@@ -243,17 +251,27 @@ public:
/** /**
* @brief Get the author of the event replied to in context of the room. * @brief Get the author of the event replied to in context of the room.
* *
* An empty Quotient::RoomMember will be returned if the EventHandler hasn't had * This is different to getting a Quotient::User object
* the room or event initialised. * as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* uses the room context and outputs the result as QVariantMap.
* *
* @param isPending if the event is pending, i.e. has not been confirmed by * An empty QVariantMap will be returned if the EventHandler hasn't had the room
* the server. * intialised. An empty user (i.e. a QVariantMap with all the correct keys
* but empty values) will be returned if the room has been set but not an event.
* *
* @return a Quotient::RoomMember object for the user. * @return a QVariantMap for the user with the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
* *
* @sa Quotient::RoomMember * @sa Quotient::User
*/ */
Quotient::RoomMember getReplyAuthor() const; QVariantMap getReplyAuthor() const;
/** /**
* @brief Output a string for the message content of the event replied to ready * @brief Output a string for the message content of the event replied to ready
@@ -342,6 +360,43 @@ public:
*/ */
QString getLocationAssetType() const; QString getLocationAssetType() const;
/**
* @brief Whether the event has any read marker for other users.
*/
bool hasReadMarkers() const;
/**
* @brief Returns a list of user read marker for the event.
*
* @param maxMarkers the maximum number of users to return. Usually the number
* of user read makers shown is limited to not clutter the UI.
* This needs to be the same as used in getNumberExcessReadMarkers
* so that the markers line up with the number displayed, i.e.
* the number of users shown plus the excess number will be
* the total number of other user read markers at an event.
*/
QVariantList getReadMarkers(int maxMarkers = 5) const;
/**
* @brief Returns the number of excess user read markers for the event.
*
* This returns a string in the form "+ x" ready for use in the UI.
*
* @param maxMarkers the maximum number of markers shown in the UI. This needs to
* be the same as used in getReadMarkers so that the value lines
* up with the number displayed, i.e. the number of users shown
* plus the excess number will be the total number of other user
* read markers at an event.
*/
QString getNumberExcessReadMarkers(int maxMarkers = 5) const;
/**
* @brief Returns a string with the names of the read markers at the event.
*
* This is in the form "x users: name 1, name 2, ...".
*/
QString getReadMarkersString() const;
private: private:
const NeoChatRoom *m_room = nullptr; const NeoChatRoom *m_room = nullptr;
const Quotient::RoomEvent *m_event = nullptr; const Quotient::RoomEvent *m_event = nullptr;

View File

@@ -3,7 +3,6 @@
#include "imagepackevent.h" #include "imagepackevent.h"
#include <QJsonObject> #include <QJsonObject>
#include <Quotient/omittable.h>
using namespace Quotient; using namespace Quotient;
@@ -17,16 +16,16 @@ ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
fromJson<Omittable<QString>>(json["pack"_ls].toObject()["attribution"_ls]), fromJson<Omittable<QString>>(json["pack"_ls].toObject()["attribution"_ls]),
}; };
} else { } else {
pack = std::nullopt; pack = none;
} }
const auto &keys = json["images"_ls].toObject().keys(); const auto &keys = json["images"_ls].toObject().keys();
for (const auto &k : keys) { for (const auto &k : keys) {
std::optional<EventContent::ImageInfo> info; Omittable<EventContent::ImageInfo> info;
if (json["images"_ls][k].toObject().contains(QStringLiteral("info"))) { if (json["images"_ls][k].toObject().contains(QStringLiteral("info"))) {
info = EventContent::ImageInfo(QUrl(json["images"_ls][k]["url"_ls].toString()), json["images"_ls][k]["info"_ls].toObject(), k); info = EventContent::ImageInfo(QUrl(json["images"_ls][k]["url"_ls].toString()), json["images"_ls][k]["info"_ls].toObject(), k);
} else { } else {
info = std::nullopt; info = none;
} }
images += ImagePackImage{ images += ImagePackImage{
k, k,

View File

@@ -26,10 +26,10 @@ public:
* @brief Defines the properties of an image pack. * @brief Defines the properties of an image pack.
*/ */
struct Pack { struct Pack {
std::optional<QString> displayName; /**< The display name of the pack. */ Quotient::Omittable<QString> displayName; /**< The display name of the pack. */
std::optional<QUrl> avatarUrl; /**< The source mxc URL for the pack avatar. */ Quotient::Omittable<QUrl> avatarUrl; /**< The source mxc URL for the pack avatar. */
std::optional<QStringList> usage; /**< An array of the usages for this pack. Possible usages are "emoticon" and "sticker". */ Quotient::Omittable<QStringList> usage; /**< An array of the usages for this pack. Possible usages are "emoticon" and "sticker". */
std::optional<QString> attribution; /**< The attribution for the pack author(s). */ Quotient::Omittable<QString> attribution; /**< The attribution for the pack author(s). */
}; };
/** /**
@@ -38,14 +38,14 @@ public:
struct ImagePackImage { struct ImagePackImage {
QString shortcode; /**< The shortcode for the image. */ QString shortcode; /**< The shortcode for the image. */
QUrl url; /**< The mxc URL for this image. */ QUrl url; /**< The mxc URL for this image. */
std::optional<QString> body; /**< An optional text body for this image. */ Quotient::Omittable<QString> body; /**< An optional text body for this image. */
std::optional<Quotient::EventContent::ImageInfo> info; /**< The ImageInfo object used for the info block of m.sticker events. */ Quotient::Omittable<Quotient::EventContent::ImageInfo> info; /**< The ImageInfo object used for the info block of m.sticker events. */
/** /**
* @brief An array of the usages for this image. * @brief An array of the usages for this image.
* *
* The possible values match those of the usage key of a pack object. * The possible values match those of the usage key of a pack object.
*/ */
std::optional<QStringList> usage; Quotient::Omittable<QStringList> usage;
}; };
/** /**
@@ -53,7 +53,7 @@ public:
* *
* @sa Pack * @sa Pack
*/ */
std::optional<Pack> pack; Quotient::Omittable<Pack> pack;
/** /**
* @brief Return a vector of images in the pack. * @brief Return a vector of images in the pack.

View File

@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <Quotient/events/simplestateevents.h>
namespace Quotient
{
// Defined so we can directly switch on type.
DEFINE_SIMPLE_STATE_EVENT(ServerAclEvent, "m.room.server_acl", bool, allow_ip_literals, "allow_ip_literals")
} // namespace Quotient

View File

@@ -14,15 +14,12 @@ Q_SCRIPTABLE RemoteActions FakeRunner::Actions()
Q_SCRIPTABLE RemoteMatches FakeRunner::Match(const QString &searchTerm) Q_SCRIPTABLE RemoteMatches FakeRunner::Match(const QString &searchTerm)
{ {
Q_UNUSED(searchTerm);
QCoreApplication::quit(); QCoreApplication::quit();
return {}; return {};
} }
Q_SCRIPTABLE void FakeRunner::Run(const QString &id, const QString &actionId) Q_SCRIPTABLE void FakeRunner::Run(const QString &id, const QString &actionId)
{ {
Q_UNUSED(id);
Q_UNUSED(actionId);
QCoreApplication::quit(); QCoreApplication::quit();
} }
@@ -36,4 +33,4 @@ FakeRunner::FakeRunner()
qDBusRegisterMetaType<RemoteImage>(); qDBusRegisterMetaType<RemoteImage>();
} }
#include "moc_fakerunner.cpp" #include "moc_fakerunner.cpp"

View File

@@ -6,9 +6,10 @@
#include <QQmlEngine> #include <QQmlEngine>
#include <Quotient/accountregistry.h> #include <Quotient/accountregistry.h>
#include <Quotient/e2ee/sssshandler.h>
#include <Quotient/keyverificationsession.h> #include <Quotient/keyverificationsession.h>
#include <Quotient/roommember.h> #if __has_include("Quotient/e2ee/sssshandler.h")
#include <Quotient/e2ee/sssshandler.h>
#endif
#include "controller.h" #include "controller.h"
#include "neochatconfig.h" #include "neochatconfig.h"
@@ -46,14 +47,10 @@ struct ForeignKeyVerificationSession {
QML_UNCREATABLE("") QML_UNCREATABLE("")
}; };
#if __has_include("Quotient/e2ee/sssshandler.h")
struct ForeignSSSSHandler { struct ForeignSSSSHandler {
Q_GADGET Q_GADGET
QML_FOREIGN(Quotient::SSSSHandler) QML_FOREIGN(Quotient::SSSSHandler)
QML_NAMED_ELEMENT(SSSSHandler) QML_NAMED_ELEMENT(SSSSHandler)
}; };
#endif
struct RoomMemberForeign {
Q_GADGET
QML_FOREIGN(Quotient::RoomMember)
QML_NAMED_ELEMENT(RoomMember)
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,11 +4,10 @@
#pragma once #pragma once
#include <Quotient/jobs/basejob.h> #include <Quotient/jobs/basejob.h>
#include <Quotient/omittable.h> #include <Quotient/omittable.h>
class NeochatChangePasswordJob : public Quotient::BaseJob class NeochatChangePasswordJob : public Quotient::BaseJob
{ {
public: public:
explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Quotient::Omittable<QJsonObject> &auth = {}); explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
}; };

View File

@@ -9,5 +9,5 @@
class NeoChatDeactivateAccountJob : public Quotient::BaseJob class NeoChatDeactivateAccountJob : public Quotient::BaseJob
{ {
public: public:
explicit NeoChatDeactivateAccountJob(const Quotient::Omittable<QJsonObject> &auth = {}); explicit NeoChatDeactivateAccountJob(const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
}; };

View File

@@ -9,5 +9,5 @@
class NeochatDeleteDeviceJob : public Quotient::BaseJob class NeochatDeleteDeviceJob : public Quotient::BaseJob
{ {
public: public:
explicit NeochatDeleteDeviceJob(const QString &deviceId, const Quotient::Omittable<QJsonObject> &auth = {}); explicit NeochatDeleteDeviceJob(const QString &deviceId, const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
}; };

View File

@@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "neochatgetcommonroomsjob.h"
using namespace Quotient;
NeochatGetCommonRoomsJob::NeochatGetCommonRoomsJob(const QString &userId)
: BaseJob(HttpVerb::Get,
QStringLiteral("GetCommonRoomsJob"),
QStringLiteral("/_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms").toLatin1(),
QUrlQuery({{QStringLiteral("user_id"), userId}}))
{
}

View File

@@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Quotient/jobs/basejob.h>
#include <Quotient/omittable.h>
// TODO: Upstream to libQuotient
class NeochatGetCommonRoomsJob : public Quotient::BaseJob
{
public:
explicit NeochatGetCommonRoomsJob(const QString &userId);
};

View File

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

View File

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

View File

@@ -5,7 +5,6 @@
#include <Quotient/accountregistry.h> #include <Quotient/accountregistry.h>
#include <Quotient/qt_connection_util.h> #include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "controller.h" #include "controller.h"
@@ -54,19 +53,14 @@ void LoginHelper::init()
m_connection = new NeoChatConnection(); m_connection = new NeoChatConnection();
} }
m_connection->resolveServer(m_matrixId); m_connection->resolveServer(m_matrixId);
connect( connectSingleShot(m_connection.get(), &Connection::loginFlowsChanged, this, [this]() {
m_connection.get(), setHomeserverReachable(true);
&Connection::loginFlowsChanged, m_testing = false;
this, Q_EMIT testingChanged();
[this]() { m_supportsSso = m_connection->supportsSso();
setHomeserverReachable(true); m_supportsPassword = m_connection->supportsPasswordAuth();
m_testing = false; Q_EMIT loginFlowsChanged();
Q_EMIT testingChanged(); });
m_supportsSso = m_connection->supportsSso();
m_supportsPassword = m_connection->supportsPasswordAuth();
Q_EMIT loginFlowsChanged();
},
Qt::SingleShotConnection);
}); });
connect(m_connection, &Connection::connected, this, [this] { connect(m_connection, &Connection::connected, this, [this] {
Q_EMIT connected(); Q_EMIT connected();
@@ -78,7 +72,7 @@ void LoginHelper::init()
account.setHomeserver(m_connection->homeserver()); account.setHomeserver(m_connection->homeserver());
account.setDeviceId(m_connection->deviceId()); account.setDeviceId(m_connection->deviceId());
account.setDeviceName(m_deviceName); account.setDeviceName(m_deviceName);
if (!Controller::instance().saveAccessTokenToKeyChain(account.userId(), m_connection->accessToken())) { if (!Controller::instance().saveAccessTokenToKeyChain(account, m_connection->accessToken())) {
qWarning() << "Couldn't save access token"; qWarning() << "Couldn't save access token";
} }
account.sync(); account.sync();
@@ -105,14 +99,9 @@ void LoginHelper::init()
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error)); Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error));
}); });
connect( connectSingleShot(m_connection.get(), &Connection::syncDone, this, [this]() {
m_connection.get(), Q_EMIT loaded();
&Connection::syncDone, });
this,
[this]() {
Q_EMIT loaded();
},
Qt::SingleShotConnection);
} }
void LoginHelper::setHomeserverReachable(bool reachable) void LoginHelper::setHomeserverReachable(bool reachable)
@@ -192,16 +181,11 @@ QUrl LoginHelper::ssoUrl() const
void LoginHelper::loginWithSso() void LoginHelper::loginWithSso()
{ {
m_connection->resolveServer(m_matrixId); m_connection->resolveServer(m_matrixId);
connect( connectSingleShot(m_connection.get(), &Connection::loginFlowsChanged, this, [this]() {
m_connection.get(), SsoSession *session = m_connection->prepareForSso(m_deviceName);
&Connection::loginFlowsChanged, m_ssoUrl = session->ssoUrl();
this, Q_EMIT ssoUrlChanged();
[this]() { });
SsoSession *session = m_connection->prepareForSso(m_deviceName);
m_ssoUrl = session->ssoUrl();
Q_EMIT ssoUrlChanged();
},
Qt::SingleShotConnection);
} }
bool LoginHelper::testing() const bool LoginHelper::testing() const

View File

@@ -2,7 +2,7 @@
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
qt_add_library(login STATIC) qt_add_library(login STATIC)
ecm_add_qml_module(login GENERATE_PLUGIN_SOURCE qt_add_qml_module(login
URI org.kde.neochat.login URI org.kde.neochat.login
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/login OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/login
QML_FILES QML_FILES

View File

@@ -13,7 +13,7 @@ LoginStep {
id: root id: root
FormCard.FormTextDelegate { FormCard.FormTextDelegate {
text: i18n("Please wait while your messages are loaded from the server. This might take a little while.") text: i18n("Please wait. This might take a little while.")
} }
FormCard.AbstractFormDelegate { FormCard.AbstractFormDelegate {
contentItem: QQC2.BusyIndicator {} contentItem: QQC2.BusyIndicator {}

View File

@@ -92,7 +92,7 @@ FormCard.FormCardPage {
} }
QQC2.ToolButton { QQC2.ToolButton {
text: i18nc("@action:button", "Log out of this account") text: i18nc("@action:button", "Remove this account")
icon.name: "edit-delete-remove" icon.name: "edit-delete-remove"
onClicked: Controller.removeConnection(modelData) onClicked: Controller.removeConnection(modelData)
display: QQC2.Button.IconOnly display: QQC2.Button.IconOnly

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