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
157 changed files with 15621 additions and 25924 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -84,7 +84,7 @@
<p xml:lang="eo">NeoChat estas babilej-apo, kiu ebligas al vi plene profiti de la Matrix-reto. Ĝi provizas al vi sekuran manieron sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj.</p>
<p xml:lang="es">NeoChat es una aplicación de chat que le permite aprovechar al máximo la red Matrix. Le proporciona un modo seguro de enviar mensajes de texto, vídeos y archivos de sonido a su familia, colegas y amigos.</p>
<p xml:lang="eu">NeoChat, Matrix sarearen abantaila guztiei probetsua ateratzeko aukera ematen dizun berriketa aplikaizo bat da. Zure familiari, kideei eta lagunei testu mezuak, bideoak eta audio fitxategiak era seguruan bidaltzeko aukera ematen dizu.</p>
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p>
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé 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="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="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>
@@ -118,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="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="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="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>
@@ -146,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="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="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="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>
@@ -204,7 +202,6 @@
<li xml:lang="nn">Klistremerke-pakkar  MSC2545</li>
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
<li xml:lang="ru">Наборы стикеров — MSC2545</li>
<li xml:lang="sl">Sticker Packs - MSC2545</li>
<li xml:lang="sv">Sticker Packs - MSC2545</li>
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
@@ -233,7 +230,6 @@
<li xml:lang="nn">Posisjonshendingar  MSC3488</li>
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
<li xml:lang="ru">События местоположения — MSC3488</li>
<li xml:lang="sl">Location Events - MSC3488</li>
<li xml:lang="sv">Location Events - MSC3488</li>
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
@@ -295,7 +291,6 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</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="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
@@ -355,7 +350,6 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</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="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
@@ -387,7 +381,6 @@
<caption xml:lang="nn">Innloggingsbilete</caption>
<caption xml:lang="pl">Ekran logowania</caption>
<caption xml:lang="pt">Ecrã de autenticação</caption>
<caption xml:lang="ru">Окно входа</caption>
<caption xml:lang="sl">Prijavni zaslon</caption>
<caption xml:lang="sv">Inloggningsfönster</caption>
<caption xml:lang="ta">நுழைவுத் திரை</caption>
@@ -401,7 +394,6 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="24.05.0" date="2024-05-23"/>
<release version="24.02.2" date="2024-04-11"/>
<release version="24.02.1" date="2024-03-21"/>
<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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@ add_library(neochat STATIC
models/customemojimodel.h
clipboard.cpp
clipboard.h
matriximageprovider.cpp
matriximageprovider.h
models/messageeventmodel.cpp
models/messageeventmodel.h
models/messagefiltermodel.cpp
@@ -175,12 +177,6 @@ add_library(neochat STATIC
foreigntypes.h
models/threepidmodel.cpp
models/threepidmodel.h
threepidaddhelper.cpp
threepidaddhelper.h
jobs/neochatadd3pidjob.cpp
jobs/neochatadd3pidjob.h
identityserverhelper.cpp
identityserverhelper.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -211,10 +207,16 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/HoverActions.qml
qml/ChatBar.qml
qml/AttachmentPane.qml
qml/ReplyPane.qml
qml/CompletionMenu.qml
qml/PieProgressBar.qml
qml/QuickFormatBar.qml
qml/EmojiPicker.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/EmojiDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
@@ -234,6 +236,9 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/ConfirmEncryptionDialog.qml
qml/RemoveSheet.qml
qml/BanSheet.qml
qml/EmojiTonesPicker.qml
qml/EmojiDelegate.qml
qml/EmojiGrid.qml
qml/RoomSearchPage.qml
qml/LocationChooser.qml
qml/TimelineView.qml
@@ -257,6 +262,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/SelectParentDialog.qml
qml/QrCodeMaximizeComponent.qml
qml/SelectSpacesDialog.qml
qml/AttachDialog.qml
qml/NotificationsView.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
@@ -274,16 +280,12 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/ConfirmLeaveDialog.qml
qml/CodeMaximizeComponent.qml
qml/EditStateDialog.qml
qml/ConsentDialog.qml
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
)
add_subdirectory(settings)
add_subdirectory(timeline)
add_subdirectory(devtools)
add_subdirectory(login)
add_subdirectory(chatbar)
if(UNIX)
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
@@ -366,18 +368,16 @@ endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
target_compile_definitions(neochat PUBLIC -DHAVE_X11)
target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp)
endif()
else()
target_compile_definitions(neochat PUBLIC -DHAVE_X11=0)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin chatbarplugin)
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin)
target_link_libraries(neochat PUBLIC
Qt::Core
Qt::Quick

View File

@@ -14,6 +14,7 @@
#include "models/actionsmodel.h"
#include "neochatconfig.h"
#include "texthandler.h"
#include "utils.h"
using namespace Quotient;
@@ -145,6 +146,26 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
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());
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -293,10 +293,7 @@ QString EventHandler::getMarkdownBody() const
}
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
QString plainBody = roomMessageEvent->plainBody();
plainBody.remove(TextRegex::removeReply);
return plainBody;
return roomMessageEvent->plainBody();
}
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
@@ -1041,11 +1038,7 @@ QString EventHandler::getReadMarkersString() const
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
for (const auto &userId : userIds) {
auto user = m_room->user(userId);
auto displayName = user->displayname(m_room);
if (displayName.isEmpty()) {
displayName = userId;
}
readMarkersString += displayName + i18nc("list separator", ", ");
readMarkersString += user->displayname(m_room) + i18nc("list separator", ", ");
}
readMarkersString.chop(2);
return readMarkersString;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@
#include "neochatroom.h"
#include <QDebug>
#include <QFont>
#ifdef HAVE_ICU
#include <QTextBoundaryFinder>
#include <QTextCharFormat>
@@ -69,8 +68,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
text += i18nc("Separate the usernames of users", " and ");
}
}
auto displayName = reaction.authors.at(i).toMap()[QStringLiteral("displayName")].toString();
text += displayName.isEmpty() ? reaction.authors.at(i).toMap()[QStringLiteral("id")].toString() : displayName;
text += reaction.authors.at(i).toMap()[QStringLiteral("displayName")].toString();
}
if (reaction.authors.count() > 3) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1156,11 +1156,7 @@ QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QStri
}
for (const auto &e : events) {
auto job = connection()->callApi<RedactEventJob>(id(), QString::fromLatin1(QUrl::toPercentEncoding(e)), connection()->generateTxnId(), reason);
#if Quotient_VERSION_MINOR > 8
co_await qCoro(job.get(), &BaseJob::finished);
#else
co_await qCoro(job, &BaseJob::finished);
#endif
if (job->error() != BaseJob::Success) {
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
break;

View File

@@ -8,11 +8,13 @@
#include <QCache>
#include <QObject>
#include <QQmlEngine>
#include <QTextCursor>
#include <QCoroTask>
#include <Quotient/user.h>
#include "enums/pushrule.h"
#include "events/pollevent.h"
#include "pollhandler.h"
namespace Quotient

View File

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

View File

@@ -7,11 +7,9 @@
"Name[ca]": "Tobias Fella",
"Name[cs]": "Tobias Fella",
"Name[de]": "Tobias Fella",
"Name[eo]": "Tobias Fella",
"Name[es]": "Tobias Fella",
"Name[eu]": "Tobias Fella",
"Name[fr]": "Tobias Fella",
"Name[gl]": "Tobias Fella",
"Name[hu]": "Tobias Fella",
"Name[ia]": "Tobias Fella",
"Name[it]": "Tobias Fella",
@@ -19,7 +17,6 @@
"Name[lv]": "Tobias Fella",
"Name[nl]": "Tobias Fella",
"Name[pl]": "Tobias Fella",
"Name[ru]": "Tobias Fella",
"Name[sl]": "Tobias Fella",
"Name[ta]": "டோபியாஸ் ஃபெல்லா",
"Name[tr]": "Tobias Fella",
@@ -33,11 +30,9 @@
"Description[ca@valencia]": "Compartix a través de NeoChat",
"Description[ca]": "Comparteix a través del NeoChat",
"Description[de]": "Über NeoChat teilen",
"Description[eo]": "Kundividi per NeoChat",
"Description[es]": "Compartir mediante NeoChat",
"Description[eu]": "Partekatu NeoChat bidez",
"Description[fr]": "Partager grâce à NeoChat",
"Description[gl]": "Compartir por NeoChat",
"Description[hu]": "Megosztás NeoChatben",
"Description[ia]": "Comparti via NeoChat",
"Description[it]": "Condividi tramite NeoChat",
@@ -45,7 +40,6 @@
"Description[lv]": "Kopīgot ar „NeoChat“",
"Description[nl]": "Delen via NeoChat",
"Description[pl]": "Udostępnij przez NeoChat",
"Description[ru]": "Опубликовать в NeoChat",
"Description[sl]": "Deli prek NeoChat",
"Description[ta]": "நியோச்சாட் மூலம் பகிர்",
"Description[tr]": "NeoChat ile Paylaş",
@@ -60,11 +54,9 @@
"Name[ca]": "NeoChat",
"Name[cs]": "NeoChat",
"Name[de]": "NeoChat",
"Name[eo]": "NeoChat",
"Name[es]": "NeoChat",
"Name[eu]": "NeoChat",
"Name[fr]": "NeoChat",
"Name[gl]": "NeoChat",
"Name[hu]": "NeoChat",
"Name[ia]": "Neochat",
"Name[it]": "NeoChat",
@@ -72,7 +64,6 @@
"Name[lv]": "NeoChat",
"Name[nl]": "NeoChat",
"Name[pl]": "NeoChat",
"Name[ru]": "NeoChat",
"Name[sl]": "NeoChat",
"Name[ta]": "நியோச்சாட்",
"Name[tr]": "NeoChat",

View File

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

View File

@@ -1,40 +0,0 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.Dialog {
id: root
required property var user
width: Math.min(Kirigami.Units.gridUnit * 24, QQC2.ApplicationWindow.window.width)
height: Kirigami.Units.gridUnit * 8
standardButtons: QQC2.Dialog.Close
title: i18nc("@title:dialog", "Start a chat")
contentItem: QQC2.Label {
text: i18n("Do you want to start a chat with %1?", root.user.displayName)
textFormat: Text.PlainText
wrapMode: Text.Wrap
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
customFooterActions: [
Kirigami.Action {
text: i18nc("@action:button", "Start Chat")
icon.name: "im-user"
onTriggered: {
root.user.requestDirectChat();
root.close();
}
}
]
}

View File

@@ -46,7 +46,7 @@ ColumnLayout {
}
onClicked: {
let imageEditor = applicationWindow().pageStack.pushDialogLayer(imageEditorPage);
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
imageEditor.newPathChanged.connect(function (newPath) {
applicationWindow().pageStack.layers.pop();
root.attachmentPath = newPath;

View File

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

View File

@@ -2,34 +2,35 @@
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.PromptDialog {
FormCard.FormCardPage {
id: root
required property NeoChatConnection connection
title: i18nc("@title:dialog", "Confirm Account Deactivation")
subtitle: i18n("Your account will be permanently disabled.\nThis cannot be undone.\nYour Matrix ID will not be available for new accounts.\nYour messages will stay available.")
title: i18nc("@title", "Deactivate Account")
dialogType: Kirigami.PromptDialog.Warning
mainItem: FormCard.FormTextFieldDelegate {
id: passwordField
label: i18nc("@label:textbox", "Password")
echoMode: TextInput.Password
horizontalPadding: 0
bottomPadding: 0
FormCard.FormHeader {
title: i18nc("@title", "Deactivate Account")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
text: i18nc("@title", "Warning")
description: i18n("Your account will be permanently disabled.\nThis cannot be undone.\nYour Matrix ID will not be available for new accounts.\nYour messages will stay available.")
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.Dialog.Cancel
FormCard.FormTextFieldDelegate {
id: passwordField
label: i18n("Password")
echoMode: TextInput.Password
}
QQC2.Button {
FormCard.FormButtonDelegate {
text: i18n("Deactivate account")
icon.name: "emblem-warning"
enabled: passwordField.text.length > 0

View File

@@ -4,25 +4,35 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.PromptDialog {
QQC2.Dialog {
id: root
title: i18nc("@title:dialog", "Activate Encryption")
subtitle: i18n("It will not be possible to deactivate the encryption after it is enabled.")
dialogType: Kirigami.PromptDialog.Warning
property NeoChatRoom room
onRejected: {
root.close();
ColumnLayout {
Kirigami.Heading {
text: i18n("Activate Encryption")
}
QQC2.Label {
text: i18n("It will not be possible to deactivate the encryption after it is enabled.")
}
}
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2)
modal: true
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.Dialog.Cancel
QQC2.Button {
text: i18n("Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: root.close()
}
QQC2.Button {
text: i18n("Activate Encryption")

View File

@@ -5,30 +5,28 @@ import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
Kirigami.PromptDialog {
Kirigami.Dialog {
id: root
required property NeoChatRoom room
width: Kirigami.Units.gridUnit * 24
title: i18nc("@title:dialog", "Confirm Leaving Room")
subtitle: root.room ? i18nc("Do you really want to leave <room name>?", "Do you really want to leave %1?", root.room.displayNameForHtml) : ""
dialogType: Kirigami.PromptDialog.Warning
onRejected: {
root.close();
contentItem: FormCard.FormTextDelegate {
text: root.room ? i18nc("Do you really want to leave <room name>?", "Do you really want to leave %1?", root.room.displayName) : ""
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.Dialog.Cancel
QQC2.Button {
customFooterActions: [
Kirigami.Action {
text: i18nc("@action:button", "Leave Room")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
icon.name: "arrow-left"
onClicked: RoomManager.leaveRoom(root.room)
onTriggered: RoomManager.leaveRoom(root.room)
}
}
]
}

View File

@@ -3,28 +3,38 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.PromptDialog {
QQC2.Dialog {
id: root
required property NeoChatConnection connection
title: i18nc("@title:dialog", "Sign out")
subtitle: i18n("Are you sure you want to sign out?")
dialogType: Kirigami.PromptDialog.Warning
onRejected: {
root.close();
ColumnLayout {
Kirigami.Heading {
text: i18n("Sign out")
}
QQC2.Label {
text: i18n("Are you sure you want to sign out?")
}
}
x: parent ? Math.round((parent.width - width) / 2) : 0
y: parent ? Math.round((parent.height - height) / 2) : 0
modal: true
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.Dialog.Cancel
QQC2.Button {
text: i18n("Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: root.close()
}
QQC2.Button {
text: i18nc("@action:button", "Sign out")
text: i18n("Sign out")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
onClicked: {
root.connection.logout(true);

View File

@@ -6,22 +6,28 @@ import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
Kirigami.PromptDialog {
Kirigami.Dialog {
id: root
property url link
title: i18nc("@title:dialog", "Open URL")
subtitle: xi18nc("@info", "Do you want to open <link>%1</link>?", root.link)
dialogType: Kirigami.PromptDialog.Warning
width: Kirigami.Units.gridUnit * 24
height: Kirigami.Units.gridUnit * 8
title: i18nc("@title", "Open URL")
standardButtons: QQC2.DialogButtonBox.Open | QQC2.DialogButtonBox.Cancel
contentItem: QQC2.Label {
text: xi18nc("@info", "Do you want to open <link>%1</link>?", root.link)
wrapMode: QQC2.Label.Wrap
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
onAccepted: {
Qt.openUrlExternally(root.link);
root.close();
}
onRejected: {
root.close();
}

View File

@@ -1,39 +0,0 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.Dialog {
id: root
required property string url
width: Math.min(Kirigami.Units.gridUnit * 24, QQC2.ApplicationWindow.window.width)
height: Kirigami.Units.gridUnit * 8
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
title: i18nc("@title:dialog", "User Consent")
contentItem: QQC2.Label {
text: i18nc("@info", "Your homeserver requires you to agree to its terms and conditions before being able to use it. Please click the button below to read them.")
wrapMode: Text.WordWrap
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
customFooterActions: [
Kirigami.Action {
text: i18nc("@action:button", "Open")
icon.name: "internet-services"
onTriggered: {
UrlHelper.openUrl(root.url);
root.close();
}
}
]
}

View File

@@ -123,12 +123,7 @@ Loader {
QQC2.MenuItem {
text: i18n("Leave Room")
icon.name: "go-previous"
onTriggered: {
Qt.createComponent('org.kde.neochat', 'ConfirmLeaveDialog').createObject(root.QQC2.ApplicationWindow.window, {
room: root.room
}).open();
}
onTriggered: RoomManager.leaveRoom(room)
}
onClosed: {
@@ -166,7 +161,7 @@ Loader {
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
id: avatar
source: room.avatarMediaId ? root.connection.makeMediaUrl("mxc://" + room.avatarMediaId) : ""
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
name: room.displayName
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3

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