Compare commits

..

1 Commits

Author SHA1 Message Date
Tobias Fella
739c96711c Invalidate room list sorting when changing event visibility settings
BUG: 455030
2022-11-27 19:16:51 +01:00
126 changed files with 22908 additions and 33732 deletions

View File

@@ -136,8 +136,8 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.7.0.tar.gz", "url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.4.0.tar.gz",
"sha256": "23ef0217926e67c8d2eb861cf91617da2f7d8d5a9ae6c62321b21448b1669210", "sha256": "0e68b3f0ce7bf521ffbdd731464d2d60d8d7a39a749b551ed26855a1707d86d1",
"x-checker-data": { "x-checker-data": {
"type": "anitya", "type": "anitya",
"project-id": 236236, "project-id": 236236,

View File

@@ -7,6 +7,7 @@ include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
# TODO enable once we can have qt6 libQuotient on the CI # TODO enable once we can have qt6 libQuotient on the CI
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml # - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml # TODO re-enable once cmark is in the CI again
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml

View File

@@ -14,11 +14,6 @@ Dependencies:
'frameworks/knotifications': '@stable' 'frameworks/knotifications': '@stable'
'libraries/kquickimageeditor': '@stable' 'libraries/kquickimageeditor': '@stable'
'frameworks/sonnet': '@stable' 'frameworks/sonnet': '@stable'
'libraries/kirigami-addons': '@latest'
'third-party/libquotient': '@latest'
'third-party/qtkeychain': '@latest'
'third-party/cmark': '@latest'
'third-party/qcoro': '@latest'
- 'on': ['Windows', 'Linux', 'FreeBSD'] - 'on': ['Windows', 'Linux', 'FreeBSD']
'require': 'require':
'frameworks/qqc2-desktop-style': '@stable' 'frameworks/qqc2-desktop-style': '@stable'

View File

@@ -7,7 +7,7 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(NeoChat) project(NeoChat)
set(PROJECT_VERSION "22.11") set(PROJECT_VERSION "22.09")
set(KF5_MIN_VERSION "5.91.0") set(KF5_MIN_VERSION "5.91.0")
set(QT_MIN_VERSION "5.15.2") set(QT_MIN_VERSION "5.15.2")
@@ -58,7 +58,6 @@ set_package_properties(KF5Kirigami2 PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Kirigami application UI framework" PURPOSE "Kirigami application UI framework"
) )
find_package(KF5KirigamiAddons 0.6 REQUIRED)
find_package(Qt${QT_MAJOR_VERSION}Keychain) find_package(Qt${QT_MAJOR_VERSION}Keychain)
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
@@ -81,6 +80,8 @@ else()
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0) ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
endif() endif()
ecm_find_qmlmodule(org.kde.kirigamiaddons.labs.mobileform 0.1)
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE) if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
find_package(KF5DBusAddons ${KF5_MIN_VERSION} REQUIRED) find_package(KF5DBusAddons ${KF5_MIN_VERSION} REQUIRED)
endif() endif()
@@ -112,7 +113,10 @@ set_package_properties(KQuickImageEditor PROPERTIES
PURPOSE "Add image editing capability to image attachments" PURPOSE "Add image editing capability to image attachments"
) )
find_package(QCoro${QT_MAJOR_VERSION} 0.4 COMPONENTS Core REQUIRED) find_package(QCoro${QT_MAJOR_VERSION} COMPONENTS Core QUIET)
if(NOT QCoro${QT_MAJOR_VERSION}_FOUND)
find_package(QCoro REQUIRED)
endif()
qcoro_enable_coroutines() qcoro_enable_coroutines()
@@ -141,7 +145,7 @@ add_definitions(-DQT_NO_FOREACH)
add_subdirectory(src) add_subdirectory(src)
if (BUILD_TESTING AND Quotient_VERSION_MINOR GREATER 6) if (BUILD_TESTING AND Quotient_VERSION_MINOR GREATER 6)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test) find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
add_subdirectory(autotests) add_subdirectory(autotests)
endif() endif()

View File

@@ -5,7 +5,9 @@
#include <QSignalSpy> #include <QSignalSpy>
#include <QTest> #include <QTest>
#define protected public // please don't hate me
#include "neochatroom.h" #include "neochatroom.h"
#undef protected
#include <connection.h> #include <connection.h>
#include <quotient_common.h> #include <quotient_common.h>
@@ -13,23 +15,12 @@
using namespace Quotient; using namespace Quotient;
class TestRoom : public NeoChatRoom
{
public:
using NeoChatRoom::NeoChatRoom;
void update(SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
};
class NeoChatRoomTest : public QObject { class NeoChatRoomTest : public QObject {
Q_OBJECT Q_OBJECT
private: private:
Connection *connection = nullptr; Connection *connection = nullptr;
TestRoom *room = nullptr; NeoChatRoom *room = nullptr;
private Q_SLOTS: private Q_SLOTS:
void initTestCase(); void initTestCase();
@@ -40,7 +31,7 @@ private Q_SLOTS:
void NeoChatRoomTest::initTestCase() void NeoChatRoomTest::initTestCase()
{ {
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org")); connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join); room = new NeoChatRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
auto json = QJsonDocument::fromJson(R"EVENT({ auto json = QJsonDocument::fromJson(R"EVENT({
"account_data": { "account_data": {
@@ -130,7 +121,7 @@ void NeoChatRoomTest::initTestCase()
} }
})EVENT"); })EVENT");
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object()); SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
room->update(std::move(roomData)); room->updateData(std::move(roomData));
} }
void NeoChatRoomTest::subtitleTextTest() void NeoChatRoomTest::subtitleTextTest()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -16,7 +16,6 @@
<name xml:lang="ca-valencia">NeoChat</name> <name xml:lang="ca-valencia">NeoChat</name>
<name xml:lang="cs">NeoChat</name> <name xml:lang="cs">NeoChat</name>
<name xml:lang="de">NeoChat</name> <name xml:lang="de">NeoChat</name>
<name xml:lang="el">NeoChat</name>
<name xml:lang="en-GB">NeoChat</name> <name xml:lang="en-GB">NeoChat</name>
<name xml:lang="es">NeoChat</name> <name xml:lang="es">NeoChat</name>
<name xml:lang="eu">NeoChat</name> <name xml:lang="eu">NeoChat</name>
@@ -35,7 +34,6 @@
<name xml:lang="pl">NeoChat</name> <name xml:lang="pl">NeoChat</name>
<name xml:lang="pt">NeoChat</name> <name xml:lang="pt">NeoChat</name>
<name xml:lang="pt-BR">NeoChat</name> <name xml:lang="pt-BR">NeoChat</name>
<name xml:lang="ru">NeoChat</name>
<name xml:lang="sk">NeoChat</name> <name xml:lang="sk">NeoChat</name>
<name xml:lang="sl">NeoChat</name> <name xml:lang="sl">NeoChat</name>
<name xml:lang="sv">NeoChat</name> <name xml:lang="sv">NeoChat</name>
@@ -51,7 +49,6 @@
<summary xml:lang="ca-valencia">Un client per a Matrix, el protocol de comunicacions descentralitzat</summary> <summary xml:lang="ca-valencia">Un client per a Matrix, el protocol de comunicacions descentralitzat</summary>
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary> <summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
<summary xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary> <summary xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
<summary xml:lang="el">Ένας πελάτης για το Matrix, το αποκεντρωμένο πρωτόκολλο επικοινωνίας</summary>
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary> <summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
<summary xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado</summary> <summary xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado</summary>
<summary xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat</summary> <summary xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat</summary>
@@ -70,7 +67,6 @@
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary> <summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary>
<summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary> <summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary>
<summary xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado</summary> <summary xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado</summary>
<summary xml:lang="ru">Клиент для Matrix — децентрализованного коммуникационного протокола</summary>
<summary xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol</summary> <summary xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol</summary>
<summary xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary> <summary xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary>
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary> <summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
@@ -85,7 +81,6 @@
<p xml:lang="ca">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p> <p xml:lang="ca">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
<p xml:lang="ca-valencia">NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics utilitzant el protocol Matrix.</p> <p xml:lang="ca-valencia">NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics utilitzant el protocol Matrix.</p>
<p xml:lang="de">NeoChat ist ein Matrix-Client. Er ermöglicht Ihnen das Senden von Textnachrichten, Videos und Audiodateien an Ihre Familie, Kollegen und Freunde unter Verwendung des Matrix-Protokolls.</p> <p xml:lang="de">NeoChat ist ein Matrix-Client. Er ermöglicht Ihnen das Senden von Textnachrichten, Videos und Audiodateien an Ihre Familie, Kollegen und Freunde unter Verwendung des Matrix-Protokolls.</p>
<p xml:lang="el">Το NeoChat είναι μια εφαρμογή του Matrix. Σας επιτρέπει να στέλνετε μηνύματα κειμένου, βίντεο και ήχο στην οικογένειά σας, σε συναδέλφους και φίλους με το πρωτόκολλο Matrix.</p>
<p xml:lang="en-GB">NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p> <p xml:lang="en-GB">NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
<p xml:lang="es">NeoChat es un cliente para Matrix. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos usando el protocolo Matrix.</p> <p xml:lang="es">NeoChat es un cliente para Matrix. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos usando el protocolo Matrix.</p>
<p xml:lang="eu">NeoChat Matrix bezero bat da. Familiari, lankideei eta lagunei testu-mezuak, bideoak eta audio-fitxategiak bidaltzeko aukera ematen du, Matrix protokoloa erabiliz.</p> <p xml:lang="eu">NeoChat Matrix bezero bat da. Familiari, lankideei eta lagunei testu-mezuak, bideoak eta audio-fitxategiak bidaltzeko aukera ematen du, Matrix protokoloa erabiliz.</p>
@@ -102,7 +97,6 @@
<p xml:lang="pl">NeoChat jest programem do Matrisa. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół poprzez protokół Matriksa.</p> <p xml:lang="pl">NeoChat jest programem do Matrisa. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół poprzez protokół Matriksa.</p>
<p xml:lang="pt">O NeoChat é um cliente do Matrix. O mesmo permite-lhe enviar mensagens de texto, ficheiros de vídeo e áudio para a sua família, colegas e amigos com o protocolo Matrix.</p> <p xml:lang="pt">O NeoChat é um cliente do Matrix. O mesmo permite-lhe enviar mensagens de texto, ficheiros de vídeo e áudio para a sua família, colegas e amigos com o protocolo Matrix.</p>
<p xml:lang="pt-BR">O NeoChat é um cliente Matrix. Ele permite a você enviar mensagens de texto, arquivos de vídeo e áudio para seus familiares, colegas e amigos usando o protocolo Matrix.</p> <p xml:lang="pt-BR">O NeoChat é um cliente Matrix. Ele permite a você enviar mensagens de texto, arquivos de vídeo e áudio para seus familiares, colegas e amigos usando o protocolo Matrix.</p>
<p xml:lang="ru">NeoChat — это клиент, поддерживающий работу с протоколом Matrix. Он позволяет отправлять текстовые сообщения, видео и аудиофайлы.</p>
<p xml:lang="sk">NeoChat je Matrix klient. Umožňuje vám posielať textové správy, videá a zvukové súbory rodine, kolegom a priateľom pomocou protokolu Matrix.</p> <p xml:lang="sk">NeoChat je Matrix klient. Umožňuje vám posielať textové správy, videá a zvukové súbory rodine, kolegom a priateľom pomocou protokolu Matrix.</p>
<p xml:lang="sl">NeoChat je odjemalec Matrixa. Dovoljuje vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, kolegom in prijateljem z uporabo protokola Matrix.</p> <p xml:lang="sl">NeoChat je odjemalec Matrixa. Dovoljuje vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, kolegom in prijateljem z uporabo protokola Matrix.</p>
<p xml:lang="sv">NeoChat är en Matrix-klient. Den låter dig skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner med användning av Matrix-protokollet.</p> <p xml:lang="sv">NeoChat är en Matrix-klient. Den låter dig skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner med användning av Matrix-protokollet.</p>
@@ -116,7 +110,6 @@
<p xml:lang="ca">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p> <p xml:lang="ca">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p> <p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
<p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p> <p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p>
<p xml:lang="el">Το Matrix είναι ένα αποκεντρωμένο πρωτόκολλο επικοινωνίας, δίνοντας πίσω στον χρήστη τον έλεγχο. Προς το παρόν το NeoChat υλοποιεί ένα μεγάλο μέρος του πρωτοκόλλου με εξαίρεση τις κρυπτογραφημένες συνομιλίες και τη συνομιλία με βίντεο.</p>
<p xml:lang="en-GB">Matrix is a decentralised communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p> <p xml:lang="en-GB">Matrix is a decentralised communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
<p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p> <p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
<p xml:lang="eu">Matrix komunikazio-protokolo deszentralizatu bat da, erabiltzaileari kontrola itzultzen diona. Gaur egun, NeoChat-ek protokoloaren zati handi bat inplementatzen du, berriketa zifratuak eta bideo berriketak izan ezik.</p> <p xml:lang="eu">Matrix komunikazio-protokolo deszentralizatu bat da, erabiltzaileari kontrola itzultzen diona. Gaur egun, NeoChat-ek protokoloaren zati handi bat inplementatzen du, berriketa zifratuak eta bideo berriketak izan ezik.</p>
@@ -133,7 +126,6 @@
<p xml:lang="pl">Matrix jest protokołem rozproszonego porozumiewania się oddający użytkownikowi jego władzę. Obecnie NeoChat obsługuje dużą część protokołu poza szyfrowanymi rozmowami tekstowymi i z obrazem.</p> <p xml:lang="pl">Matrix jest protokołem rozproszonego porozumiewania się oddający użytkownikowi jego władzę. Obecnie NeoChat obsługuje dużą część protokołu poza szyfrowanymi rozmowami tekstowymi i z obrazem.</p>
<p xml:lang="pt">O Matrix é um protocolo de comunicações descentralizado, colocando de novo o utilizador no poder. De momento, o NeoChat implementa uma boa parte do protocolo, com a excepção das conversas encriptadas e as conversas de vídeo.</p> <p xml:lang="pt">O Matrix é um protocolo de comunicações descentralizado, colocando de novo o utilizador no poder. De momento, o NeoChat implementa uma boa parte do protocolo, com a excepção das conversas encriptadas e as conversas de vídeo.</p>
<p xml:lang="pt-BR">O Matrix é um protocolo de comunicação descentralizado, colocando o usuário de volta no controle. Atualmente o NeoChat implementa grande parte do protocolo com a exceção de bate-papos criptografados e bate-papo por vídeo.</p> <p xml:lang="pt-BR">O Matrix é um protocolo de comunicação descentralizado, colocando o usuário de volta no controle. Atualmente o NeoChat implementa grande parte do protocolo com a exceção de bate-papos criptografados e bate-papo por vídeo.</p>
<p xml:lang="ru">Matrix — это децентрализованный коммуникационный протокол, возвращающий пользователю контроль над своими данными. В настоящее время в приложении NeoChat реализована поддержка большей части протокола, за исключением зашифрованных чатов и видеочата.</p>
<p xml:lang="sk">Matrix je decentralizovaný komunikačný protokol, ktorý používateľovi vracia kontrolu. V súčasnosti NeoChat implementuje veľkú časť protokolu s výnimkou šifrovaných chatov a videohovorov.</p> <p xml:lang="sk">Matrix je decentralizovaný komunikačný protokol, ktorý používateľovi vracia kontrolu. V súčasnosti NeoChat implementuje veľkú časť protokolu s výnimkou šifrovaných chatov a videohovorov.</p>
<p xml:lang="sl">Matrix je decentraliziran komunikacijski protokol, kjer ima uporabnik uporabnik kontrolo rabe. Trenutno ima NeoChat izveden velik del protokola z izjemo šifriranih klepetov in video klepetov.</p> <p xml:lang="sl">Matrix je decentraliziran komunikacijski protokol, kjer ima uporabnik uporabnik kontrolo rabe. Trenutno ima NeoChat izveden velik del protokola z izjemo šifriranih klepetov in video klepetov.</p>
<p xml:lang="sv">Matrix är ett decentraliserat kommunikationsprotokoll, som ger tillbaka kontrollen till användaren. För närvarande implementerar NeoChat en stor del av protokollet, med undantag för krypterad chatt och videochatt.</p> <p xml:lang="sv">Matrix är ett decentraliserat kommunikationsprotokoll, som ger tillbaka kontrollen till användaren. För närvarande implementerar NeoChat en stor del av protokollet, med undantag för krypterad chatt och videochatt.</p>
@@ -146,8 +138,7 @@
<p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p> <p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
<p xml:lang="ca">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p> <p xml:lang="ca">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
<p xml:lang="ca-valencia">NeoChat funciona en els mòbils i en l'escriptori, proporcionant una experiència d'usuari coherent.</p> <p xml:lang="ca-valencia">NeoChat funciona en els mòbils i en l'escriptori, proporcionant una experiència d'usuari coherent.</p>
<p xml:lang="de">NeoChat funktioniert sowohl auf Mobilgeräten als auch auf dem PC und bietet ein einheitliches Benutzererlebnis.</p> <p xml:lang="de">NeoChat funktioniert sowohl auf dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </p>
<p xml:lang="el">Το NeoChat λειτουργεί και στα κινητά και στους υπολογιστές γραφείου παρέχοντας μια αδιάλειπτη εμπειρία χρήσης.</p>
<p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p> <p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
<p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p> <p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
<p xml:lang="eu">NeoChat mugikorretan eta mahaigainean dabil, erabiltzaile esperientzia koherentea eskainiz.</p> <p xml:lang="eu">NeoChat mugikorretan eta mahaigainean dabil, erabiltzaile esperientzia koherentea eskainiz.</p>
@@ -164,7 +155,6 @@
<p xml:lang="pl">NeoChat działa zarówno na urządzeniach przenośnych jak i biurkowych, zapewniając spójne wrażenia użytkownika</p> <p xml:lang="pl">NeoChat działa zarówno na urządzeniach przenośnych jak i biurkowych, zapewniając spójne wrażenia użytkownika</p>
<p xml:lang="pt">O NeoChat funciona tanto em dispositivos móveis como no computador, fornecendo uma experiência de utilizador consistente.</p> <p xml:lang="pt">O NeoChat funciona tanto em dispositivos móveis como no computador, fornecendo uma experiência de utilizador consistente.</p>
<p xml:lang="pt-BR">O NeoChat funciona tanto no celular como no computador enquanto fornece uma experiência consistente ao usuário.</p> <p xml:lang="pt-BR">O NeoChat funciona tanto no celular como no computador enquanto fornece uma experiência consistente ao usuário.</p>
<p xml:lang="ru">NeoChat работает как на мобильных устройствах, так и на настольных компьютерах, обеспечивая единый пользовательский интерфейс.</p>
<p xml:lang="sk">NeoChat funguje na mobilných aj stolových počítačoch a poskytuje konzistentný používateľský zážitok.</p> <p xml:lang="sk">NeoChat funguje na mobilných aj stolových počítačoch a poskytuje konzistentný používateľský zážitok.</p>
<p xml:lang="sl">NeoChat deluje tako na mobilnih kot na namiznih platformah z zagotavljanjem konsistentne uporabniške izkušnje.</p> <p xml:lang="sl">NeoChat deluje tako na mobilnih kot na namiznih platformah z zagotavljanjem konsistentne uporabniške izkušnje.</p>
<p xml:lang="sv">NeoChat fungerar både på mobil och skrivbord och tillhandahåller en konsekvent användarupplevelse.</p> <p xml:lang="sv">NeoChat fungerar både på mobil och skrivbord och tillhandahåller en konsekvent användarupplevelse.</p>
@@ -185,7 +175,6 @@
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name> <developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
<developer_name xml:lang="cs">Komunita KDE</developer_name> <developer_name xml:lang="cs">Komunita KDE</developer_name>
<developer_name xml:lang="de">Die KDE-Gemeinschaft</developer_name> <developer_name xml:lang="de">Die KDE-Gemeinschaft</developer_name>
<developer_name xml:lang="el">Η Κοινότητα του KDE</developer_name>
<developer_name xml:lang="en-GB">The KDE Community</developer_name> <developer_name xml:lang="en-GB">The KDE Community</developer_name>
<developer_name xml:lang="es">La comunidad KDE</developer_name> <developer_name xml:lang="es">La comunidad KDE</developer_name>
<developer_name xml:lang="eu">KDE komunitatea</developer_name> <developer_name xml:lang="eu">KDE komunitatea</developer_name>
@@ -204,7 +193,6 @@
<developer_name xml:lang="pl">Społeczność KDE</developer_name> <developer_name xml:lang="pl">Społeczność KDE</developer_name>
<developer_name xml:lang="pt">A Comunidade do KDE</developer_name> <developer_name xml:lang="pt">A Comunidade do KDE</developer_name>
<developer_name xml:lang="pt-BR">A comunidade KDE</developer_name> <developer_name xml:lang="pt-BR">A comunidade KDE</developer_name>
<developer_name xml:lang="ru">Сообщество KDE</developer_name>
<developer_name xml:lang="sk">KDE Komunita</developer_name> <developer_name xml:lang="sk">KDE Komunita</developer_name>
<developer_name xml:lang="sl">Skupnost KDE</developer_name> <developer_name xml:lang="sl">Skupnost KDE</developer_name>
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name> <developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
@@ -216,7 +204,6 @@
<project_license>GPL-3.0</project_license> <project_license>GPL-3.0</project_license>
<custom> <custom>
<value key="KDE::matrix">#neochat:kde.org</value> <value key="KDE::matrix">#neochat:kde.org</value>
<value key="KDE::windows_store">https://www.microsoft.com/store/apps/9PNXWVNRC29H</value>
</custom> </custom>
<launchable type="desktop-id">org.kde.neochat.desktop</launchable> <launchable type="desktop-id">org.kde.neochat.desktop</launchable>
<screenshots> <screenshots>
@@ -231,9 +218,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="22.11" date="2022-11-30">
<url>https://plasma-mobile.org/2022/11/30/plasma-mobile-gear-22-11/</url>
</release>
<release version="22.09" date="2022-09-27"> <release version="22.09" date="2022-09-27">
<url>https://www.plasma-mobile.org/2022/09/27/plasma-mobile-gear-22-09/</url> <url>https://www.plasma-mobile.org/2022/09/27/plasma-mobile-gear-22-09/</url>
</release> </release>

View File

@@ -9,7 +9,6 @@ Name[ca]=NeoChat
Name[ca@valencia]=NeoChat Name[ca@valencia]=NeoChat
Name[cs]=NeoChat Name[cs]=NeoChat
Name[de]=NeoChat Name[de]=NeoChat
Name[el]=NeoChat
Name[en_GB]=NeoChat Name[en_GB]=NeoChat
Name[es]=NeoChat Name[es]=NeoChat
Name[eu]=NeoChat Name[eu]=NeoChat
@@ -30,7 +29,6 @@ Name[pl]=NeoChat
Name[pt]=NeoChat Name[pt]=NeoChat
Name[pt_BR]=NeoChat Name[pt_BR]=NeoChat
Name[ro]=NeoChat Name[ro]=NeoChat
Name[ru]=NeoChat
Name[sk]=NeoChat Name[sk]=NeoChat
Name[sl]=NeoChat Name[sl]=NeoChat
Name[sv]=NeoChat Name[sv]=NeoChat
@@ -46,7 +44,6 @@ GenericName[ca]=Client de Matrix
GenericName[ca@valencia]=Client de Matrix GenericName[ca@valencia]=Client de Matrix
GenericName[cs]=Klient protokolu Matrix GenericName[cs]=Klient protokolu Matrix
GenericName[de]=Matrix-Programm GenericName[de]=Matrix-Programm
GenericName[el]=Εφαρμογή του Matrix
GenericName[en_GB]=Matrix Client GenericName[en_GB]=Matrix Client
GenericName[es]=Cliente para Matrix GenericName[es]=Cliente para Matrix
GenericName[eu]=Matrix bezeroa GenericName[eu]=Matrix bezeroa
@@ -67,7 +64,6 @@ GenericName[pl]=Program Matriksa
GenericName[pt]=Cliente de Matrix GenericName[pt]=Cliente de Matrix
GenericName[pt_BR]=Cliente Matrix GenericName[pt_BR]=Cliente Matrix
GenericName[ro]=Client Matrix GenericName[ro]=Client Matrix
GenericName[ru]=Клиент Matrix
GenericName[sk]=Matrix Client GenericName[sk]=Matrix Client
GenericName[sl]=Odjemalec Matrix GenericName[sl]=Odjemalec Matrix
GenericName[sv]=Matrix-klient GenericName[sv]=Matrix-klient
@@ -82,7 +78,6 @@ Comment[az]=Matrix protokolu üçün müştəri
Comment[ca]=Client per al protocol Matrix Comment[ca]=Client per al protocol Matrix
Comment[ca@valencia]=Client per al protocol Matrix Comment[ca@valencia]=Client per al protocol Matrix
Comment[de]=Programm für das Matrix-Protokoll Comment[de]=Programm für das Matrix-Protokoll
Comment[el]=Πελάτης για το πρωτόκολλο Matrix
Comment[en_GB]=Client for the Matrix protocol Comment[en_GB]=Client for the Matrix protocol
Comment[es]=Cliente para el protocolo Matrix Comment[es]=Cliente para el protocolo Matrix
Comment[eu]=Matrix protokolorako bezeroa Comment[eu]=Matrix protokolorako bezeroa
@@ -103,7 +98,6 @@ Comment[pl]=Program obsługi protokołu Matriksa
Comment[pt]=Cliente para o protocolo Matrix Comment[pt]=Cliente para o protocolo Matrix
Comment[pt_BR]=Cliente para o protocolo Matrix Comment[pt_BR]=Cliente para o protocolo Matrix
Comment[ro]=Client pentru protocolul Matrix Comment[ro]=Client pentru protocolul Matrix
Comment[ru]=Клиент для протокола Matrix
Comment[sk]=Klient protokolu Matrix Comment[sk]=Klient protokolu Matrix
Comment[sl]=Odjemalec za protokol Matrix Comment[sl]=Odjemalec za protokol Matrix
Comment[sv]=Klient för protokollet Matrix Comment[sv]=Klient för protokollet Matrix

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,122 +0,0 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
<!ENTITY % Italian "INCLUDE">
]>
<!--
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<refentry lang="&language;">
<refentryinfo>
<title
>Manuale utente di NeoChat</title>
<author
><firstname
>Carl</firstname
><surname
>Schwan</surname
> <contrib
>Pagina man di NeoChat.</contrib
> <email
>carl@carlschwan.eu</email
></author>
<date
>1/11/2022</date>
<releaseinfo
>22.09</releaseinfo>
<productname
>NeoChat</productname>
</refentryinfo>
<refmeta>
<refentrytitle>
<command
>neochat</command>
</refentrytitle>
<manvolnum
>1</manvolnum>
</refmeta>
<refnamediv>
<refname
>neochat</refname>
<refpurpose
>Client per l'interazione con il protocollo di messaggistica matrix</refpurpose>
</refnamediv>
<!-- body begins here -->
<refsynopsisdiv id='synopsis'>
<cmdsynopsis
><command
>neochat</command
> <arg choice="opt"
><replaceable
>URI</replaceable
></arg
> </cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
<title
>Descrizione</title>
<para
><command
>neochat</command
> è un'applicazione di chat per il protocollo Matrix che funziona sia su desktop che su dispositivi mobili. </para>
</refsect1>
<refsect1 id="options"
><title
>Opzioni</title>
<variablelist>
<varlistentry>
<term
><option
>URI</option
></term>
<listitem>
<para
>L'URI matrix per un utente o una stanza. ad esempio matrix:u/utente:esempio.org e matrix:r/stanza:esempio.org. Questo farà in modo che NeoChat provi ad aprire la stanza o la conversazione specificata. </para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="bug">
<title
>Segnalazione bug</title>
<para
>Puoi segnalare bug e richiedere funzionalità su <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General"
>https://bugs.kde.org/enter_bug.cgi? product=NeoChat&amp;component=General</ulink
></para>
</refsect1>
<refsect1>
<title
>Vedi anche</title>
<simplelist>
<member
>Un elenco di domande frequenti su Matrix <ulink url="https://matrix.org/faq/"
>https://matrix.org/faq/</ulink
> </member>
<member
>kf5options(7)</member>
<member
>qt5options(7)</member>
</simplelist>
</refsect1>
<refsect1 id="copyright"
><title
>Copyright</title>
<para
>Copyright &copy; 2020-2022 Tobias Fella </para>
<para
>Copyright &copy; 2020-2022 Carl Schwan </para>
<para
>Licenza: GNU General Public Version 3 o successiva &lt;<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
>&gt;</para>
</refsect1>
</refentry>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

@@ -7,7 +7,6 @@ add_library(neochat STATIC
controller.cpp controller.cpp
actionshandler.cpp actionshandler.cpp
emojimodel.cpp emojimodel.cpp
emojitones.cpp
customemojimodel.cpp customemojimodel.cpp
clipboard.cpp clipboard.cpp
matriximageprovider.cpp matriximageprovider.cpp
@@ -20,7 +19,6 @@ add_library(neochat STATIC
neochatroom.cpp neochatroom.cpp
neochatuser.cpp neochatuser.cpp
userlistmodel.cpp userlistmodel.cpp
userfiltermodel.cpp
publicroomlistmodel.cpp publicroomlistmodel.cpp
userdirectorylistmodel.cpp userdirectorylistmodel.cpp
keywordnotificationrulemodel.cpp keywordnotificationrulemodel.cpp
@@ -46,7 +44,6 @@ add_library(neochat STATIC
serverlistmodel.cpp serverlistmodel.cpp
statemodel.cpp statemodel.cpp
filetransferpseudojob.cpp filetransferpseudojob.cpp
searchmodel.cpp
) )
add_executable(neochat-app add_executable(neochat-app
@@ -70,7 +67,7 @@ endif()
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png) ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON}) target_sources(neochat PRIVATE ${NEOCHAT_ICON})
if(NOT ANDROID) if(NOT ANDROID)
target_sources(neochat PRIVATE colorschemer.cpp) target_sources(neochat PRIVATE colorschemer.cpp)
@@ -93,7 +90,12 @@ else()
endif() endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR}) target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::SonnetCore KF5::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core) target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::SonnetCore KF5::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES})
if(TARGET QCoro5::Coro)
target_link_libraries(neochat PUBLIC QCoro5::Coro)
else()
target_link_libraries(neochat PUBLIC QCoro::QCoro)
endif()
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc) kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
if(NEOCHAT_FLATPAK) if(NEOCHAT_FLATPAK)
@@ -163,14 +165,6 @@ if(ANDROID)
"download" "download"
"smiley" "smiley"
"tools-check-spelling" "tools-check-spelling"
"username-copy"
"system-switch-user"
"bookmark-new"
"bookmark-remove"
"favorite"
"window-new"
"globe"
"visibility"
) )
else() else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF5::KIOWidgets) target_link_libraries(neochat PUBLIC Qt::Widgets KF5::KIOWidgets)

View File

@@ -17,8 +17,9 @@
#include "controller.h" #include "controller.h"
#include "customemojimodel.h" #include "customemojimodel.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatuser.h" #include "neochatroom.h"
#include "roommanager.h" #include "roommanager.h"
#include "neochatuser.h"
using namespace Quotient; using namespace Quotient;
@@ -138,10 +139,6 @@ void ActionsHandler::handleMessage()
handledText = CustomEmojiModel::instance().preprocessText(handledText); handledText = CustomEmojiModel::instance().preprocessText(handledText);
handledText = markdownToHTML(handledText); handledText = markdownToHTML(handledText);
if (handledText.count("<p>") == 1 && handledText.count("</p>") == 1) {
handledText.remove("<p>");
handledText.remove("</p>");
}
if (handledText.length() == 0) { if (handledText.length() == 0) {
return; return;

View File

@@ -7,8 +7,7 @@
#include <events/roommessageevent.h> #include <events/roommessageevent.h>
#include "neochatroom.h" class NeoChatRoom;
class CustomEmojiModel; class CustomEmojiModel;
class NeoChatRoom; class NeoChatRoom;

View File

@@ -6,7 +6,6 @@
#include "controller.h" #include "controller.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "neochatuser.h" #include "neochatuser.h"
#include "roommanager.h"
#include <events/roommemberevent.h> #include <events/roommemberevent.h>
#include <events/roompowerlevelsevent.h> #include <events/roompowerlevelsevent.h>
@@ -194,9 +193,8 @@ QVector<ActionsModel::Action> actions{
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text)); i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString(); return QString();
} }
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text); if (Controller::instance().activeConnection()->room(text) || Controller::instance().activeConnection()->roomByAlias(text)) {
if (targetRoom) { Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
RoomManager::instance().enterRoom(dynamic_cast<NeoChatRoom *>(targetRoom));
return QString(); return QString();
} }
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text)); Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));

View File

@@ -5,6 +5,7 @@
#include <QQmlFile> #include <QQmlFile>
#include <QQmlFileSelector> #include <QQmlFileSelector>
#include <QQuickTextDocument>
#include <QStringBuilder> #include <QStringBuilder>
#include <QSyntaxHighlighter> #include <QSyntaxHighlighter>
#include <QTextBlock> #include <QTextBlock>
@@ -15,6 +16,7 @@
#include <Sonnet/Settings> #include <Sonnet/Settings>
#include "actionsmodel.h" #include "actionsmodel.h"
#include "completionmodel.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "roomlistmodel.h" #include "roomlistmodel.h"
@@ -133,11 +135,7 @@ int ChatDocumentHandler::completionStartIndex() const
return 0; return 0;
} }
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) const auto &cursor = cursorPosition();
const long long cursor = cursorPosition();
#else
const auto cursor = cursorPosition();
#endif
const auto &text = m_room->chatBoxText(); const auto &text = m_room->chatBoxText();
auto start = std::min(cursor, text.size()) - 1; auto start = std::min(cursor, text.size()) - 1;
while (start > -1) { while (start > -1) {
@@ -201,7 +199,7 @@ void ChatDocumentHandler::setRoom(NeoChatRoom *room)
void ChatDocumentHandler::complete(int index) void ChatDocumentHandler::complete(int index)
{ {
if (m_completionModel->autoCompletionType() == CompletionModel::User) { if (m_completionModel->autoCompletionType() == ChatDocumentHandler::User) {
auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Text).toString(); auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Text).toString();
auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString(); auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString();
auto text = m_room->chatBoxText(); auto text = m_room->chatBoxText();
@@ -215,7 +213,7 @@ void ChatDocumentHandler::complete(int index)
cursor.setKeepPositionOnInsert(true); cursor.setKeepPositionOnInsert(true);
m_room->mentions()->push_back({cursor, name, 0, 0, id}); m_room->mentions()->push_back({cursor, name, 0, 0, id});
m_highlighter->rehighlight(); m_highlighter->rehighlight();
} else if (m_completionModel->autoCompletionType() == CompletionModel::Command) { } else if (m_completionModel->autoCompletionType() == ChatDocumentHandler::Command) {
auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString(); auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString();
auto text = m_room->chatBoxText(); auto text = m_room->chatBoxText();
auto at = text.lastIndexOf(QLatin1Char('/')); auto at = text.lastIndexOf(QLatin1Char('/'));
@@ -223,7 +221,7 @@ void ChatDocumentHandler::complete(int index)
cursor.setPosition(at); cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
cursor.insertText(QStringLiteral("/%1 ").arg(command)); cursor.insertText(QStringLiteral("/%1 ").arg(command));
} else if (m_completionModel->autoCompletionType() == CompletionModel::Room) { } else if (m_completionModel->autoCompletionType() == ChatDocumentHandler::Room) {
auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString(); auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString();
auto text = m_room->chatBoxText(); auto text = m_room->chatBoxText();
auto at = text.lastIndexOf(QLatin1Char('#'), cursorPosition() - 1); auto at = text.lastIndexOf(QLatin1Char('#'), cursorPosition() - 1);
@@ -236,7 +234,7 @@ void ChatDocumentHandler::complete(int index)
cursor.setKeepPositionOnInsert(true); cursor.setKeepPositionOnInsert(true);
m_room->mentions()->push_back({cursor, alias, 0, 0, alias}); m_room->mentions()->push_back({cursor, alias, 0, 0, alias});
m_highlighter->rehighlight(); m_highlighter->rehighlight();
} else if (m_completionModel->autoCompletionType() == CompletionModel::Emoji) { } else if (m_completionModel->autoCompletionType() == ChatDocumentHandler::Emoji) {
auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString(); auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString();
auto text = m_room->chatBoxText(); auto text = m_room->chatBoxText();
auto at = text.lastIndexOf(QLatin1Char(':')); auto at = text.lastIndexOf(QLatin1Char(':'));

View File

@@ -4,15 +4,16 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <QQuickTextDocument>
#include <QTextCursor> #include <QTextCursor>
#include "completionmodel.h"
#include "userlistmodel.h" #include "userlistmodel.h"
class QTextDocument; class QTextDocument;
class QQuickTextDocument;
class NeoChatRoom; class NeoChatRoom;
class SyntaxHighlighter; class SyntaxHighlighter;
class CompletionModel;
class ChatDocumentHandler : public QObject class ChatDocumentHandler : public QObject
{ {
@@ -27,6 +28,15 @@ class ChatDocumentHandler : public QObject
Q_PROPERTY(NeoChatRoom *room READ room NOTIFY roomChanged) Q_PROPERTY(NeoChatRoom *room READ room NOTIFY roomChanged)
public: public:
enum AutoCompletionType {
User,
Room,
Emoji,
Command,
None,
};
Q_ENUM(AutoCompletionType)
explicit ChatDocumentHandler(QObject *parent = nullptr); explicit ChatDocumentHandler(QObject *parent = nullptr);
[[nodiscard]] QQuickTextDocument *document() const; [[nodiscard]] QQuickTextDocument *document() const;
@@ -70,7 +80,9 @@ private:
SyntaxHighlighter *m_highlighter = nullptr; SyntaxHighlighter *m_highlighter = nullptr;
CompletionModel::AutoCompletionType m_completionType = CompletionModel::None; AutoCompletionType m_completionType = None;
CompletionModel *m_completionModel = nullptr; CompletionModel *m_completionModel = nullptr;
}; };
Q_DECLARE_METATYPE(ChatDocumentHandler::AutoCompletionType);

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-2.0-or-later // SPDX-License-Identifier: LGPL-2.0-or-later
#include "collapsestateproxymodel.h" #include "collapsestateproxymodel.h"
#include "messageeventmodel.h"
#include <KLocalizedString> #include <KLocalizedString>

View File

@@ -10,6 +10,7 @@
#include "customemojimodel.h" #include "customemojimodel.h"
#include "emojimodel.h" #include "emojimodel.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "roomlistmodel.h"
#include "userlistmodel.h" #include "userlistmodel.h"
CompletionModel::CompletionModel(QObject *parent) CompletionModel::CompletionModel(QObject *parent)
@@ -41,7 +42,7 @@ void CompletionModel::setText(const QString &text, const QString &fullText)
int CompletionModel::rowCount(const QModelIndex &parent) const int CompletionModel::rowCount(const QModelIndex &parent) const
{ {
Q_UNUSED(parent); Q_UNUSED(parent);
if (m_autoCompletionType == None) { if (m_autoCompletionType == ChatDocumentHandler::None) {
return 0; return 0;
} }
return m_filterModel->rowCount(); return m_filterModel->rowCount();
@@ -53,7 +54,7 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
return {}; return {};
} }
auto filterIndex = m_filterModel->index(index.row(), 0); auto filterIndex = m_filterModel->index(index.row(), 0);
if (m_autoCompletionType == User) { if (m_autoCompletionType == ChatDocumentHandler::User) {
if (role == Text) { if (role == Text) {
return m_filterModel->data(filterIndex, UserListModel::NameRole); return m_filterModel->data(filterIndex, UserListModel::NameRole);
} }
@@ -65,7 +66,7 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
} }
} }
if (m_autoCompletionType == Command) { if (m_autoCompletionType == ChatDocumentHandler::Command) {
if (role == Text) { if (role == Text) {
return m_filterModel->data(filterIndex, ActionsModel::Prefix).toString() + QStringLiteral(" ") return m_filterModel->data(filterIndex, ActionsModel::Prefix).toString() + QStringLiteral(" ")
+ m_filterModel->data(filterIndex, ActionsModel::Parameters).toString(); + m_filterModel->data(filterIndex, ActionsModel::Parameters).toString();
@@ -80,7 +81,7 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
return m_filterModel->data(filterIndex, ActionsModel::Prefix); return m_filterModel->data(filterIndex, ActionsModel::Prefix);
} }
} }
if (m_autoCompletionType == Room) { if (m_autoCompletionType == ChatDocumentHandler::Room) {
if (role == Text) { if (role == Text) {
return m_filterModel->data(filterIndex, RoomListModel::DisplayNameRole); return m_filterModel->data(filterIndex, RoomListModel::DisplayNameRole);
} }
@@ -91,7 +92,7 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
return m_filterModel->data(filterIndex, RoomListModel::AvatarRole); return m_filterModel->data(filterIndex, RoomListModel::AvatarRole);
} }
} }
if (m_autoCompletionType == Emoji) { if (m_autoCompletionType == ChatDocumentHandler::Emoji) {
if (role == Text) { if (role == Text) {
return m_filterModel->data(filterIndex, CustomEmojiModel::DisplayRole); return m_filterModel->data(filterIndex, CustomEmojiModel::DisplayRole);
} }
@@ -101,9 +102,6 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
if (role == ReplacedText) { if (role == ReplacedText) {
return m_filterModel->data(filterIndex, CustomEmojiModel::ReplacedTextRole); return m_filterModel->data(filterIndex, CustomEmojiModel::ReplacedTextRole);
} }
if (role == Subtitle) {
return m_filterModel->data(filterIndex, EmojiModel::DescriptionRole);
}
} }
return {}; return {};
@@ -127,7 +125,7 @@ void CompletionModel::updateCompletion()
m_filterModel->setSecondaryFilterRole(UserListModel::NameRole); m_filterModel->setSecondaryFilterRole(UserListModel::NameRole);
m_filterModel->setFullText(m_fullText); m_filterModel->setFullText(m_fullText);
m_filterModel->setFilterText(m_text); m_filterModel->setFilterText(m_text);
m_autoCompletionType = User; m_autoCompletionType = ChatDocumentHandler::User;
m_filterModel->invalidate(); m_filterModel->invalidate();
} else if (text().startsWith(QLatin1Char('/'))) { } else if (text().startsWith(QLatin1Char('/'))) {
m_filterModel->setSourceModel(&ActionsModel::instance()); m_filterModel->setSourceModel(&ActionsModel::instance());
@@ -135,10 +133,10 @@ void CompletionModel::updateCompletion()
m_filterModel->setSecondaryFilterRole(-1); m_filterModel->setSecondaryFilterRole(-1);
m_filterModel->setFullText(m_fullText); m_filterModel->setFullText(m_fullText);
m_filterModel->setFilterText(m_text.mid(1)); m_filterModel->setFilterText(m_text.mid(1));
m_autoCompletionType = Command; m_autoCompletionType = ChatDocumentHandler::Command;
m_filterModel->invalidate(); m_filterModel->invalidate();
} else if (text().startsWith(QLatin1Char('#'))) { } else if (text().startsWith(QLatin1Char('#'))) {
m_autoCompletionType = Room; m_autoCompletionType = ChatDocumentHandler::Room;
m_filterModel->setSourceModel(m_roomListModel); m_filterModel->setSourceModel(m_roomListModel);
m_filterModel->setFilterRole(RoomListModel::CanonicalAliasRole); m_filterModel->setFilterRole(RoomListModel::CanonicalAliasRole);
m_filterModel->setSecondaryFilterRole(RoomListModel::DisplayNameRole); m_filterModel->setSecondaryFilterRole(RoomListModel::DisplayNameRole);
@@ -148,15 +146,15 @@ void CompletionModel::updateCompletion()
} else if (text().startsWith(QLatin1Char(':')) } else if (text().startsWith(QLatin1Char(':'))
&& (m_fullText.indexOf(QLatin1Char(':'), 1) == -1 && (m_fullText.indexOf(QLatin1Char(':'), 1) == -1
|| (m_fullText.indexOf(QLatin1Char(' ')) != -1 && m_fullText.indexOf(QLatin1Char(':'), 1) > m_fullText.indexOf(QLatin1Char(' '), 1)))) { || (m_fullText.indexOf(QLatin1Char(' ')) != -1 && m_fullText.indexOf(QLatin1Char(':'), 1) > m_fullText.indexOf(QLatin1Char(' '), 1)))) {
m_autoCompletionType = ChatDocumentHandler::Emoji;
m_filterModel->setSourceModel(m_emojiModel); m_filterModel->setSourceModel(m_emojiModel);
m_autoCompletionType = Emoji;
m_filterModel->setFilterRole(CustomEmojiModel::Name); m_filterModel->setFilterRole(CustomEmojiModel::Name);
m_filterModel->setSecondaryFilterRole(EmojiModel::DescriptionRole); m_filterModel->setSecondaryFilterRole(-1);
m_filterModel->setFullText(m_fullText); m_filterModel->setFullText(m_fullText);
m_filterModel->setFilterText(m_text); m_filterModel->setFilterText(m_text);
m_filterModel->invalidate(); m_filterModel->invalidate();
} else { } else {
m_autoCompletionType = None; m_autoCompletionType = ChatDocumentHandler::None;
} }
beginResetModel(); beginResetModel();
endResetModel(); endResetModel();
@@ -173,12 +171,12 @@ void CompletionModel::setRoom(NeoChatRoom *room)
Q_EMIT roomChanged(); Q_EMIT roomChanged();
} }
CompletionModel::AutoCompletionType CompletionModel::autoCompletionType() const ChatDocumentHandler::AutoCompletionType CompletionModel::autoCompletionType() const
{ {
return m_autoCompletionType; return m_autoCompletionType;
} }
void CompletionModel::setAutoCompletionType(AutoCompletionType autoCompletionType) void CompletionModel::setAutoCompletionType(ChatDocumentHandler::AutoCompletionType autoCompletionType)
{ {
m_autoCompletionType = autoCompletionType; m_autoCompletionType = autoCompletionType;
Q_EMIT autoCompletionTypeChanged(); Q_EMIT autoCompletionTypeChanged();

View File

@@ -7,7 +7,7 @@
#include <KConcatenateRowsProxyModel> #include <KConcatenateRowsProxyModel>
#include "roomlistmodel.h" #include "chatdocumenthandler.h"
class CompletionProxyModel; class CompletionProxyModel;
class UserListModel; class UserListModel;
@@ -19,19 +19,10 @@ class CompletionModel : public QAbstractListModel
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString text READ text NOTIFY textChanged) Q_PROPERTY(QString text READ text NOTIFY textChanged)
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged) Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
Q_PROPERTY(AutoCompletionType autoCompletionType READ autoCompletionType NOTIFY autoCompletionTypeChanged); Q_PROPERTY(ChatDocumentHandler::AutoCompletionType autoCompletionType READ autoCompletionType NOTIFY autoCompletionTypeChanged);
Q_PROPERTY(RoomListModel *roomListModel READ roomListModel WRITE setRoomListModel NOTIFY roomListModelChanged); Q_PROPERTY(RoomListModel *roomListModel READ roomListModel WRITE setRoomListModel NOTIFY roomListModelChanged);
public: public:
enum AutoCompletionType {
User,
Room,
Emoji,
Command,
None,
};
Q_ENUM(AutoCompletionType)
enum Roles { enum Roles {
Text = Qt::DisplayRole, Text = Qt::DisplayRole,
Subtitle, Subtitle,
@@ -56,7 +47,7 @@ public:
RoomListModel *roomListModel() const; RoomListModel *roomListModel() const;
void setRoomListModel(RoomListModel *roomListModel); void setRoomListModel(RoomListModel *roomListModel);
AutoCompletionType autoCompletionType() const; ChatDocumentHandler::AutoCompletionType autoCompletionType() const;
Q_SIGNALS: Q_SIGNALS:
void textChanged(); void textChanged();
@@ -69,12 +60,11 @@ private:
QString m_fullText; QString m_fullText;
CompletionProxyModel *m_filterModel; CompletionProxyModel *m_filterModel;
NeoChatRoom *m_room = nullptr; NeoChatRoom *m_room = nullptr;
AutoCompletionType m_autoCompletionType = None; ChatDocumentHandler::AutoCompletionType m_autoCompletionType = ChatDocumentHandler::None;
void setAutoCompletionType(AutoCompletionType autoCompletionType); void setAutoCompletionType(ChatDocumentHandler::AutoCompletionType autoCompletionType);
UserListModel *m_userListModel; UserListModel *m_userListModel;
RoomListModel *m_roomListModel; RoomListModel *m_roomListModel;
KConcatenateRowsProxyModel *m_emojiModel; KConcatenateRowsProxyModel *m_emojiModel;
}; };
Q_DECLARE_METATYPE(CompletionModel::AutoCompletionType);

View File

@@ -18,22 +18,7 @@ bool CompletionProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &so
&& sourceModel() && sourceModel()
->data(sourceModel()->index(sourceRow, 0), secondaryFilterRole()) ->data(sourceModel()->index(sourceRow, 0), secondaryFilterRole())
.toString() .toString()
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
.startsWith(QStringView(m_filterText).sliced(1), Qt::CaseInsensitive));
#else
.startsWith(m_filterText.midRef(1), Qt::CaseInsensitive)); .startsWith(m_filterText.midRef(1), Qt::CaseInsensitive));
#endif
}
bool CompletionProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
if (m_secondaryFilterRole == -1)
return QSortFilterProxyModel::lessThan(source_left, source_right);
bool left_primary = sourceModel()->data(source_left, filterRole()).toString().startsWith(m_filterText, Qt::CaseInsensitive);
bool right_primary = sourceModel()->data(source_right, filterRole()).toString().startsWith(m_filterText, Qt::CaseInsensitive);
if (left_primary != right_primary)
return left_primary;
return QSortFilterProxyModel::lessThan(source_left, source_right);
} }
int CompletionProxyModel::secondaryFilterRole() const int CompletionProxyModel::secondaryFilterRole() const

View File

@@ -13,7 +13,6 @@ class CompletionProxyModel : public QSortFilterProxyModel
public: public:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
int secondaryFilterRole() const; int secondaryFilterRole() const;
void setSecondaryFilterRole(int role); void setSecondaryFilterRole(int role);

View File

@@ -19,6 +19,7 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QImageReader> #include <QImageReader>
#include <QNetworkProxy> #include <QNetworkProxy>
#include <QQuickItem>
#include <QQuickTextDocument> #include <QQuickTextDocument>
#include <QQuickWindow> #include <QQuickWindow>
#include <QStandardPaths> #include <QStandardPaths>
@@ -233,6 +234,13 @@ void Controller::showWindow()
WindowController::instance().showAndRaiseWindow(QString()); WindowController::instance().showAndRaiseWindow(QString());
} }
inline QString accessTokenFileName(const AccountSettings &account)
{
QString fileName = account.userId();
fileName.replace(':', '_');
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + '/' + fileName;
}
void Controller::loginWithAccessToken(const QString &serverAddr, const QString &user, const QString &token, const QString &deviceName) void Controller::loginWithAccessToken(const QString &serverAddr, const QString &user, const QString &token, const QString &deviceName)
{ {
if (user.isEmpty() || token.isEmpty()) { if (user.isEmpty() || token.isEmpty()) {
@@ -273,6 +281,7 @@ void Controller::logout(Connection *conn, bool serverSideLogout)
} }
SettingsGroup("Accounts").remove(conn->userId()); SettingsGroup("Accounts").remove(conn->userId());
QFile(accessTokenFileName(AccountSettings(conn->userId()))).remove();
QKeychain::DeletePasswordJob job(qAppName()); QKeychain::DeletePasswordJob job(qAppName());
job.setAutoDelete(true); job.setAutoDelete(true);
@@ -365,7 +374,15 @@ void Controller::invokeLogin()
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) { if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
accessToken = accessTokenLoadingJob->binaryData(); accessToken = accessTokenLoadingJob->binaryData();
} else { } else {
return; // No access token from the keychain, try token file
// TODO FIXME this code is racy since the file might have
// already been removed. But since the other code do a blocking
// dbus call, the probability are not high that it will happen.
// loadAccessTokenFromFile is also mostly legacy nowadays
accessToken = loadAccessTokenFromFile(account);
if (accessToken.isEmpty()) {
return;
}
} }
auto connection = new Connection(account.homeserver()); auto connection = new Connection(account.homeserver());
@@ -399,6 +416,21 @@ void Controller::invokeLogin()
} }
} }
QByteArray Controller::loadAccessTokenFromFile(const AccountSettings &account)
{
QFile accountTokenFile{accessTokenFileName(account)};
if (accountTokenFile.open(QFile::ReadOnly)) {
if (accountTokenFile.size() < 1024) {
return accountTokenFile.readAll();
}
qWarning() << "File" << accountTokenFile.fileName() << "is" << accountTokenFile.size() << "bytes long - too long for a token, ignoring it.";
}
qWarning() << "Could not open access token file" << accountTokenFile.fileName();
return {};
}
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account) QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
{ {
qDebug() << "Reading access token from the keychain for" << account.userId(); qDebug() << "Reading access token from the keychain for" << account.userId();
@@ -410,6 +442,24 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const Accoun
if (job->error() == QKeychain::Error::NoError) { if (job->error() == QKeychain::Error::NoError) {
return; return;
} }
if (job->error() == QKeychain::Error::EntryNotFound) {
// no access token from the keychain, try token file
auto accessToken = loadAccessTokenFromFile(account);
if (!accessToken.isEmpty()) {
qDebug() << "Migrating the access token from file to the keychain for " << account.userId();
bool removed = false;
bool saved = saveAccessTokenToKeyChain(account, accessToken);
if (saved) {
QFile accountTokenFile{accessTokenFileName(account)};
removed = accountTokenFile.remove();
}
if (!(saved && removed)) {
qDebug() << "Migrating the access token from the file to the keychain "
"failed";
}
return;
}
}
switch (job->error()) { switch (job->error()) {
case QKeychain::EntryNotFound: case QKeychain::EntryNotFound:
@@ -434,6 +484,22 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const Accoun
return job; return job;
} }
bool Controller::saveAccessTokenToFile(const AccountSettings &account, const QByteArray &accessToken)
{
// (Re-)Make a dedicated file for access_token.
QFile accountTokenFile{accessTokenFileName(account)};
accountTokenFile.remove(); // Just in case
auto fileDir = QFileInfo(accountTokenFile).dir();
if (!((fileDir.exists() || fileDir.mkpath(".")) && accountTokenFile.open(QFile::WriteOnly))) {
Q_EMIT errorOccured("I/O Denied: Cannot save access token.");
} else {
accountTokenFile.write(accessToken);
return true;
}
return false;
}
bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken) bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
{ {
qDebug() << "Save the access token to the keychain for " << account.userId(); qDebug() << "Save the access token to the keychain for " << account.userId();
@@ -448,7 +514,7 @@ bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const
if (job.error()) { if (job.error()) {
qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString()); qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString());
return false; return saveAccessTokenToFile(account, accessToken);
} }
return true; return true;
} }
@@ -742,15 +808,11 @@ void Controller::setApplicationProxy()
proxy.setType(QNetworkProxy::HttpProxy); proxy.setType(QNetworkProxy::HttpProxy);
proxy.setHostName(cfg->proxyHost()); proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort()); proxy.setPort(cfg->proxyPort());
proxy.setUser(cfg->proxyUser());
proxy.setPassword(cfg->proxyPassword());
break; break;
case 2: // SOCKS 5 case 2: // SOCKS 5
proxy.setType(QNetworkProxy::Socks5Proxy); proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName(cfg->proxyHost()); proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort()); proxy.setPort(cfg->proxyPort());
proxy.setUser(cfg->proxyUser());
proxy.setPassword(cfg->proxyPassword());
break; break;
case 0: // System Default case 0: // System Default
default: default:

View File

@@ -68,6 +68,7 @@ public:
[[nodiscard]] bool supportSystemTray() const; [[nodiscard]] bool supportSystemTray() const;
bool saveAccessTokenToFile(const Quotient::AccountSettings &account, const QByteArray &accessToken);
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken); bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
int activeConnectionIndex() const; int activeConnectionIndex() const;
@@ -110,6 +111,7 @@ private:
bool m_busy = false; bool m_busy = false;
TrayIcon *m_trayIcon = nullptr; TrayIcon *m_trayIcon = nullptr;
static QByteArray loadAccessTokenFromFile(const Quotient::AccountSettings &account);
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account); QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
void loadSettings(); void loadSettings();

View File

@@ -11,10 +11,6 @@ struct CustomEmoji {
QString name; // with :semicolons: QString name; // with :semicolons:
QString url; // mxc:// QString url; // mxc://
QRegularExpression regexp; QRegularExpression regexp;
Q_GADGET
Q_PROPERTY(QString unicode MEMBER url)
Q_PROPERTY(QString name MEMBER name)
}; };
class CustomEmojiModel : public QAbstractListModel class CustomEmojiModel : public QAbstractListModel
@@ -29,7 +25,6 @@ public:
MxcUrl = 50, MxcUrl = 50,
DisplayRole = 51, DisplayRole = 51,
ReplacedTextRole = 52, ReplacedTextRole = 52,
DescriptionRole = 53, // also invalid, reserved
}; };
Q_ENUM(Roles); Q_ENUM(Roles);

View File

@@ -4,13 +4,12 @@
#include <QVariant> #include <QVariant>
#include "emojimodel.h" #include "emojimodel.h"
#include "emojitones.h"
#include <QDebug> #include <QDebug>
#include <algorithm> #include <algorithm>
#include "customemojimodel.h"
#include <KLocalizedString> #include <KLocalizedString>
#include <qnamespace.h>
EmojiModel::EmojiModel(QObject *parent) EmojiModel::EmojiModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
@@ -49,8 +48,6 @@ QVariant EmojiModel::data(const QModelIndex &index, int role) const
return QStringLiteral("invalid"); return QStringLiteral("invalid");
case DisplayRole: case DisplayRole:
return QStringLiteral("%2 :%1:").arg(emoji.shortName, emoji.unicode); return QStringLiteral("%2 :%1:").arg(emoji.shortName, emoji.unicode);
case DescriptionRole:
return emoji.description;
} }
} }
return {}; return {};
@@ -61,19 +58,16 @@ QHash<int, QByteArray> EmojiModel::roleNames() const
return {{ShortNameRole, "shortName"}, {UnicodeRole, "unicode"}}; return {{ShortNameRole, "shortName"}, {UnicodeRole, "unicode"}};
} }
QMultiHash<QString, QVariant> EmojiModel::_tones = {
#include "emojitones.h"
};
QVariantList EmojiModel::history() const QVariantList EmojiModel::history() const
{ {
return m_settings.value("Editor/emojis", QVariantList()).toList(); return m_settings.value("Editor/emojis", QVariantList()).toList();
} }
QVariantList EmojiModel::filterModel(const QString &filter, bool limit) QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
{
auto emojis = CustomEmojiModel::instance().filterModel(filter);
emojis += filterModelNoCustom(filter, limit);
return emojis;
}
QVariantList EmojiModel::filterModelNoCustom(const QString &filter, bool limit)
{ {
QVariantList result; QVariantList result;
@@ -88,6 +82,7 @@ QVariantList EmojiModel::filterModelNoCustom(const QString &filter, bool limit)
} }
} }
} }
return result; return result;
} }
@@ -115,28 +110,12 @@ QVariantList EmojiModel::emojis(Category category) const
if (category == History) { if (category == History) {
return history(); return history();
} }
if (category == HistoryNoCustom) {
QVariantList list;
for (const auto &e : history()) {
auto emoji = qvariant_cast<Emoji>(e);
if (!emoji.isCustom) {
list.append(e);
}
}
return list;
}
if (category == Custom) {
return CustomEmojiModel::instance().filterModel({});
}
return _emojis[category]; return _emojis[category];
} }
QVariantList EmojiModel::tones(const QString &baseEmoji) const QVariantList EmojiModel::tones(const QString &baseEmoji) const
{ {
if (baseEmoji.endsWith("tone")) { return _tones.values(baseEmoji);
return EmojiTones::_tones.values(baseEmoji.split(":")[0]);
}
return EmojiTones::_tones.values(baseEmoji);
} }
QHash<EmojiModel::Category, QVariantList> EmojiModel::_emojis; QHash<EmojiModel::Category, QVariantList> EmojiModel::_emojis;
@@ -144,11 +123,21 @@ QHash<EmojiModel::Category, QVariantList> EmojiModel::_emojis;
QVariantList EmojiModel::categories() const QVariantList EmojiModel::categories() const
{ {
return QVariantList{ return QVariantList{
// {QVariantMap{
// {"category", EmojiModel::Search},
// {"name", i18nc("Search for emojis", "Search")},
// {"emoji", QStringLiteral("🔎")},
// }},
{QVariantMap{ {QVariantMap{
{"category", EmojiModel::HistoryNoCustom}, {"category", EmojiModel::History},
{"name", i18nc("Previously used emojis", "History")}, {"name", i18nc("Previously used emojis", "History")},
{"emoji", QStringLiteral("⌛️")}, {"emoji", QStringLiteral("⌛️")},
}}, }},
{QVariantMap{
{"category", EmojiModel::Custom},
{"name", i18nc("'Custom' is a category of emoji", "Custom")},
{"emoji", QStringLiteral("😏")},
}},
{QVariantMap{ {QVariantMap{
{"category", EmojiModel::Smileys}, {"category", EmojiModel::Smileys},
{"name", i18nc("'Smileys' is a category of emoji", "Smileys")}, {"name", i18nc("'Smileys' is a category of emoji", "Smileys")},
@@ -196,23 +185,3 @@ QVariantList EmojiModel::categories() const
}}, }},
}; };
} }
QVariantList EmojiModel::categoriesWithCustom() const
{
auto cats = categories();
cats.removeAt(0);
cats.insert(0,
QVariantMap{
{"category", EmojiModel::History},
{"name", i18nc("Previously used emojis", "History")},
{"emoji", QStringLiteral("⌛️")},
});
cats.insert(1,
QVariantMap{
{"category", EmojiModel::Custom},
{"name", i18nc("'Custom' is a category of emoji", "Custom")},
{"emoji", QStringLiteral("🖼️")},
});
;
return cats;
}

View File

@@ -8,18 +8,12 @@
#include <QSettings> #include <QSettings>
struct Emoji { struct Emoji {
Emoji(QString unicode, QString shortname, bool isCustom = false) Emoji(QString u, QString s, bool isCustom = false)
: unicode(std::move(unicode)) : unicode(std::move(std::move(u)))
, shortName(std::move(shortname)) , shortName(std::move(std::move(s)))
, isCustom(isCustom) , isCustom(isCustom)
{ {
} }
Emoji(QString unicode, QString shortname, QString description)
: unicode(std::move(unicode))
, shortName(std::move(shortname))
, description(std::move(description))
{
}
Emoji() = default; Emoji() = default;
friend QDataStream &operator<<(QDataStream &arch, const Emoji &object) friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
@@ -39,13 +33,11 @@ struct Emoji {
QString unicode; QString unicode;
QString shortName; QString shortName;
QString description;
bool isCustom = false; bool isCustom = false;
Q_GADGET Q_GADGET
Q_PROPERTY(QString unicode MEMBER unicode) Q_PROPERTY(QString unicode MEMBER unicode)
Q_PROPERTY(QString shortName MEMBER shortName) Q_PROPERTY(QString shortName MEMBER shortName)
Q_PROPERTY(QString description MEMBER description)
Q_PROPERTY(bool isCustom MEMBER isCustom) Q_PROPERTY(bool isCustom MEMBER isCustom)
}; };
@@ -57,7 +49,6 @@ class EmojiModel : public QAbstractListModel
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged) Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
Q_PROPERTY(QVariantList categories READ categories CONSTANT) Q_PROPERTY(QVariantList categories READ categories CONSTANT)
Q_PROPERTY(QVariantList categoriesWithCustom READ categoriesWithCustom CONSTANT)
public: public:
static EmojiModel &instance() static EmojiModel &instance()
@@ -72,16 +63,13 @@ public:
InvalidRole = 50, InvalidRole = 50,
DisplayRole = 51, DisplayRole = 51,
ReplacedTextRole = 52, ReplacedTextRole = 52,
DescriptionRole = 53,
}; };
Q_ENUM(RoleNames); Q_ENUM(RoleNames);
enum Category { enum Category {
Custom, Custom,
Search, Search,
SearchNoCustom,
History, History,
HistoryNoCustom,
Smileys, Smileys,
People, People,
Nature, Nature,
@@ -101,14 +89,11 @@ public:
Q_INVOKABLE QVariantList history() const; Q_INVOKABLE QVariantList history() const;
Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true); Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true);
Q_INVOKABLE static QVariantList filterModelNoCustom(const QString &filter, bool limit = true);
Q_INVOKABLE QVariantList emojis(Category category) const; Q_INVOKABLE QVariantList emojis(Category category) const;
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const; Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
QVariantList categories() const; QVariantList categories() const;
QVariantList categoriesWithCustom() const;
Q_SIGNALS: Q_SIGNALS:
void historyChanged(); void historyChanged();
@@ -118,6 +103,7 @@ public Q_SLOTS:
private: private:
static QHash<Category, QVariantList> _emojis; static QHash<Category, QVariantList> _emojis;
static QMultiHash<QString, QVariant> _tones;
// TODO: Port away from QSettings // TODO: Port away from QSettings
QSettings m_settings; QSettings m_settings;

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +0,0 @@
// SPDX-FileCopyrightText: None
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "emojitones.h"
#include "emojimodel.h"
QMultiHash<QString, QVariant> EmojiTones::_tones = {
#include "emojitones_data.h"
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,9 +15,7 @@
KeywordNotificationRuleModel::KeywordNotificationRuleModel(QObject *parent) KeywordNotificationRuleModel::KeywordNotificationRuleModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {
if (Controller::instance().activeConnection()) { controllerConnectionChanged();
controllerConnectionChanged();
}
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &KeywordNotificationRuleModel::controllerConnectionChanged); connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &KeywordNotificationRuleModel::controllerConnectionChanged);
} }

View File

@@ -14,6 +14,8 @@
#include "controller.h" #include "controller.h"
#include <QUrl>
#include <KLocalizedString> #include <KLocalizedString>
using namespace Quotient; using namespace Quotient;
@@ -156,8 +158,8 @@ void Login::login()
// Some servers do not have a .well_known file. So we login via the username part from the mxid, // Some servers do not have a .well_known file. So we login via the username part from the mxid,
// rather than with the full mxid, as that would lead to an invalid user. // rather than with the full mxid, as that would lead to an invalid user.
auto username = m_matrixId.mid(1, m_matrixId.indexOf(":") - 1); QStringRef username(&m_matrixId, 1, m_matrixId.indexOf(":") - 1);
m_connection->loginWithPassword(username, m_password, m_deviceName, QString()); m_connection->loginWithPassword(username.toString(), m_password, m_deviceName, QString());
} }
bool Login::supportsPassword() const bool Login::supportsPassword() const
@@ -181,8 +183,8 @@ void Login::loginWithSso()
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() { connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() {
SsoSession *session = m_connection->prepareForSso(m_deviceName); SsoSession *session = m_connection->prepareForSso(m_deviceName);
m_ssoUrl = session->ssoUrl(); m_ssoUrl = session->ssoUrl();
Q_EMIT ssoUrlChanged();
}); });
Q_EMIT ssoUrlChanged();
} }
bool Login::testing() const bool Login::testing() const

View File

@@ -59,7 +59,6 @@
#include "neochatroom.h" #include "neochatroom.h"
#include "neochatuser.h" #include "neochatuser.h"
#include "notificationsmanager.h" #include "notificationsmanager.h"
#include "searchmodel.h"
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
#include "pollhandler.h" #include "pollhandler.h"
#endif #endif
@@ -72,7 +71,6 @@
#include "spacehierarchycache.h" #include "spacehierarchycache.h"
#include "urlhelper.h" #include "urlhelper.h"
#include "userdirectorylistmodel.h" #include "userdirectorylistmodel.h"
#include "userfiltermodel.h"
#include "userlistmodel.h" #include "userlistmodel.h"
#include "webshortcutmodel.h" #include "webshortcutmodel.h"
#include "windowcontroller.h" #include "windowcontroller.h"
@@ -221,7 +219,6 @@ int main(int argc, char *argv[])
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel"); qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel"); qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel");
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel"); qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
qmlRegisterType<UserFilterModel>("org.kde.neochat", 1, 0, "UserFilterModel");
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel"); qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel"); qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
qmlRegisterType<ServerListModel>("org.kde.neochat", 1, 0, "ServerListModel"); qmlRegisterType<ServerListModel>("org.kde.neochat", 1, 0, "ServerListModel");
@@ -231,7 +228,6 @@ int main(int argc, char *argv[])
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer"); qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel"); qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel"); qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler"); qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
#endif #endif
@@ -269,32 +265,6 @@ int main(int argc, char *argv[])
#endif #endif
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
#ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique);
service.connect(&service,
&KDBusService::activateRequested,
&RoomManager::instance(),
[&engine](const QStringList &arguments, const QString &workingDirectory) {
Q_UNUSED(workingDirectory);
QWindow *window = windowFromEngine(&engine);
KWindowSystem::updateStartupId(window);
WindowController::instance().showAndRaiseWindow(QString());
// Open matrix uri
if (arguments.isEmpty()) {
return;
}
auto args = arguments;
args.removeFirst();
for (const auto &arg : args) {
RoomManager::instance().openResource(arg);
}
});
#endif
engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit); QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory()); engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
@@ -324,6 +294,31 @@ int main(int argc, char *argv[])
QDBusConnection::sessionBus().registerObject("/RoomRunner", &runner, QDBusConnection::ExportScriptableContents); QDBusConnection::sessionBus().registerObject("/RoomRunner", &runner, QDBusConnection::ExportScriptableContents);
#endif #endif
#ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique);
service.connect(&service,
&KDBusService::activateRequested,
&RoomManager::instance(),
[&engine](const QStringList &arguments, const QString &workingDirectory) {
Q_UNUSED(workingDirectory);
QWindow *window = windowFromEngine(&engine);
KWindowSystem::updateStartupId(window);
WindowController::instance().showAndRaiseWindow(QString());
// Open matrix uri
if (arguments.isEmpty()) {
return;
}
auto args = arguments;
args.removeFirst();
for (const auto &arg : args) {
RoomManager::instance().openResource(arg);
}
});
#endif
QWindow *window = windowFromEngine(&engine); QWindow *window = windowFromEngine(&engine);
WindowController::instance().setWindow(window); WindowController::instance().setWindow(window);

View File

@@ -66,7 +66,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
roles[AuthorDisplayNameRole] = "authorDisplayName"; roles[AuthorDisplayNameRole] = "authorDisplayName";
roles[IsNameChangeRole] = "isNameChange"; roles[IsNameChangeRole] = "isNameChange";
roles[IsAvatarChangeRole] = "isAvatarChange"; roles[IsAvatarChangeRole] = "isAvatarChange";
roles[IsRedactedRole] = "isRedacted";
return roles; return roles;
} }
@@ -895,9 +894,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
} }
return false; return false;
} }
if (role == IsRedactedRole) {
return evt.isRedacted();
}
return {}; return {};
} }
@@ -937,24 +933,12 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId()
if (content.contains("m.new_content")) { if (content.contains("m.new_content")) {
// The message has been edited so we have to return the id of the original message instead of the replacement // The message has been edited so we have to return the id of the original message instead of the replacement
eventId = content["m.relates_to"].toObject()["event_id"].toString(); eventId = content["m.relates_to"].toObject()["event_id"].toString();
e = eventCast<const RoomMessageEvent>(m_currentRoom->findInTimeline(eventId)->event());
if (!e) {
return {};
}
content = e->contentJson();
} else { } else {
// For any message that isn't an edit return the id of the current message // For any message that isn't an edit return the id of the current message
eventId = (*it)->id(); eventId = (*it)->id();
} }
targetMessage.insert("event_id", eventId); targetMessage.insert("event_id", eventId);
targetMessage.insert("formattedBody", content["formatted_body"].toString()); targetMessage.insert("formattedBody", content["formatted_body"].toString());
// keep reply relationship
if (content.contains("m.relates_to")) {
targetMessage.insert("m.relates_to", content["m.relates_to"].toObject());
}
// Need to get the message from the original eventId or body will have * on the front // Need to get the message from the original eventId or body will have * on the front
QModelIndex idx = index(eventIDToIndex(eventId), 0); QModelIndex idx = index(eventIDToIndex(eventId), 0);
targetMessage.insert("message", idx.data(Qt::UserRole + 2)); targetMessage.insert("message", idx.data(Qt::UserRole + 2));

View File

@@ -71,7 +71,6 @@ public:
AuthorDisplayNameRole, AuthorDisplayNameRole,
IsNameChangeRole, IsNameChangeRole,
IsAvatarChangeRole, IsAvatarChangeRole,
IsRedactedRole,
LastRole, // Keep this last LastRole, // Keep this last
}; };
Q_ENUM(EventRoles) Q_ENUM(EventRoles)

View File

@@ -25,10 +25,6 @@ MessageFilterModel::MessageFilterModel(QObject *parent)
beginResetModel(); beginResetModel();
endResetModel(); endResetModel();
}); });
connect(NeoChatConfig::self(), &NeoChatConfig::ShowDeletedMessagesChanged, this, [this] {
beginResetModel();
endResetModel();
});
} }
bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
@@ -43,15 +39,12 @@ bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour
if (index.data(MessageEventModel::IsAvatarChangeRole).toBool() && !NeoChatConfig::self()->showAvatarUpdate()) { if (index.data(MessageEventModel::IsAvatarChangeRole).toBool() && !NeoChatConfig::self()->showAvatarUpdate()) {
return false; return false;
} }
if (index.data(MessageEventModel::IsRedactedRole).toBool() && !NeoChatConfig::self()->showDeletedMessages()) {
return false;
}
if (specialMarks == EventStatus::Hidden || specialMarks == EventStatus::Replaced) { if (specialMarks == EventStatus::Hidden || specialMarks == EventStatus::Replaced) {
return false; return false;
} }
const auto eventType = index.data(MessageEventModel::EventTypeRole).toInt(); const QString eventType = index.data(MessageEventModel::EventTypeRole).toString();
if (eventType == MessageEventModel::Other) { if (eventType == MessageEventModel::Other) {
return false; return false;

View File

@@ -7,7 +7,6 @@ Name[ca]=NeoChat
Name[ca@valencia]=NeoChat Name[ca@valencia]=NeoChat
Name[cs]=NeoChat Name[cs]=NeoChat
Name[de]=NeoChat Name[de]=NeoChat
Name[el]=NeoChat
Name[en_GB]=NeoChat Name[en_GB]=NeoChat
Name[es]=NeoChat Name[es]=NeoChat
Name[eu]=NeoChat Name[eu]=NeoChat
@@ -28,7 +27,6 @@ Name[pl]=NeoChat
Name[pt]=NeoChat Name[pt]=NeoChat
Name[pt_BR]=NeoChat Name[pt_BR]=NeoChat
Name[ro]=NeoChat Name[ro]=NeoChat
Name[ru]=NeoChat
Name[sk]=NeoChat Name[sk]=NeoChat
Name[sl]=NeoChat Name[sl]=NeoChat
Name[sv]=NeoChat Name[sv]=NeoChat
@@ -45,7 +43,6 @@ Comment[ca]=Un client per a Matrix, el protocol de comunicacions descentralitzat
Comment[ca@valencia]=Un client per a Matrix, el protocol de comunicacions descentralitzat Comment[ca@valencia]=Un client per a Matrix, el protocol de comunicacions descentralitzat
Comment[cs]=Klient pro decentralizovaný komunikační protokol matrix Comment[cs]=Klient pro decentralizovaný komunikační protokol matrix
Comment[de]=Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll Comment[de]=Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll
Comment[el]=Ένας πελάτης για το Matrix, το αποκεντρωμένο πρωτόκολλο επικοινωνίας
Comment[en_GB]=A client for matrix, the decentralised communication protocol Comment[en_GB]=A client for matrix, the decentralised communication protocol
Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado
Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat
@@ -66,7 +63,6 @@ Comment[pl]=Program do obsługi matriksa, rozproszonego protokołu porozumiewani
Comment[pt]=Um cliente para o Matrix, o protocolo descentralizado de comunicações Comment[pt]=Um cliente para o Matrix, o protocolo descentralizado de comunicações
Comment[pt_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado Comment[pt_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado
Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată
Comment[ru]=Клиент для Matrix — децентрализованного коммуникационного протокола
Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol
Comment[sl]=Odjemalec za decentralizirani komunikacijski protokol matrix Comment[sl]=Odjemalec za decentralizirani komunikacijski protokol matrix
Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokollet Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokollet
@@ -84,7 +80,6 @@ Name[ca]=Missatge nou
Name[ca@valencia]=Missatge nou Name[ca@valencia]=Missatge nou
Name[cs]=Nová zpráva Name[cs]=Nová zpráva
Name[de]=Neue Nachricht Name[de]=Neue Nachricht
Name[el]=Νέο μήνυμα
Name[en_GB]=New message Name[en_GB]=New message
Name[es]=Nuevo mensaje Name[es]=Nuevo mensaje
Name[eu]=Mezu berria Name[eu]=Mezu berria
@@ -105,7 +100,6 @@ Name[pl]=Nowa wiadomość
Name[pt]=Nova mensagem Name[pt]=Nova mensagem
Name[pt_BR]=Nova mensagem Name[pt_BR]=Nova mensagem
Name[ro]=Mesaj nou Name[ro]=Mesaj nou
Name[ru]=Новое сообщение
Name[sk]=Nová správa Name[sk]=Nová správa
Name[sl]=Novo sporočilo Name[sl]=Novo sporočilo
Name[sv]=Nytt meddelande Name[sv]=Nytt meddelande
@@ -119,8 +113,7 @@ Comment[ar]=توجد رسالة جديدة
Comment[az]=Yeni ismarıc var Comment[az]=Yeni ismarıc var
Comment[ca]=Hi ha un missatge nou Comment[ca]=Hi ha un missatge nou
Comment[ca@valencia]=Hi ha un missatge nou Comment[ca@valencia]=Hi ha un missatge nou
Comment[de]=Es gibt eine neue Nachricht Comment[de]=Es ist eine neue Nachricht vorhanden
Comment[el]=Υπάρχει ένα νέο μήνυμα
Comment[en_GB]=There is a new message Comment[en_GB]=There is a new message
Comment[es]=Hay un mensaje nuevo Comment[es]=Hay un mensaje nuevo
Comment[eu]=Mezu berri bat dago Comment[eu]=Mezu berri bat dago
@@ -141,7 +134,6 @@ Comment[pl]=Dostępna jest nowa wiadomość
Comment[pt]=Tem uma mensagem nova Comment[pt]=Tem uma mensagem nova
Comment[pt_BR]=Existe uma nova mensagem Comment[pt_BR]=Existe uma nova mensagem
Comment[ro]=Este un mesaj nou Comment[ro]=Este un mesaj nou
Comment[ru]=Доступно новое сообщение
Comment[sk]=Je nová správa Comment[sk]=Je nová správa
Comment[sl]=Prišlo je novo sporočilo Comment[sl]=Prišlo je novo sporočilo
Comment[sv]=Det finns ett nytt meddelande Comment[sv]=Det finns ett nytt meddelande
@@ -160,7 +152,6 @@ Name[ca]=Invitació nova
Name[ca@valencia]=Invitació nova Name[ca@valencia]=Invitació nova
Name[cs]=Nová pozvánka Name[cs]=Nová pozvánka
Name[de]=Neue Einladung Name[de]=Neue Einladung
Name[el]=Νέα πρόσκληση
Name[en_GB]=New Invitation Name[en_GB]=New Invitation
Name[es]=Nueva invitación Name[es]=Nueva invitación
Name[eu]=Gonbidapen berria Name[eu]=Gonbidapen berria
@@ -177,7 +168,6 @@ Name[pa]=ਨਵਾਂ ਸੱਦਾ
Name[pl]=Nowe zaproszenie Name[pl]=Nowe zaproszenie
Name[pt]=Novo Convite Name[pt]=Novo Convite
Name[pt_BR]=Novo convite Name[pt_BR]=Novo convite
Name[ru]=Новое приглашение
Name[sl]=Novo povabilo Name[sl]=Novo povabilo
Name[sv]=Ny inbjudan Name[sv]=Ny inbjudan
Name[ta]=புதிய அழைப்பிதழ் Name[ta]=புதிய அழைப்பிதழ்
@@ -191,7 +181,6 @@ Comment[ca]=Hi ha una invitació nova a una sala
Comment[ca@valencia]=Hi ha una invitació nova a una sala Comment[ca@valencia]=Hi ha una invitació nova a una sala
Comment[cs]=Máte novou pozvánku do místnosti Comment[cs]=Máte novou pozvánku do místnosti
Comment[de]=Es gibt eine neue Einladung zu einem Raum Comment[de]=Es gibt eine neue Einladung zu einem Raum
Comment[el]=Υπάρχει μια νέα πρόσκληση σε μια αίθουσα
Comment[en_GB]=There is a new invitation to a room Comment[en_GB]=There is a new invitation to a room
Comment[es]=Hay una nueva invitación a una sala Comment[es]=Hay una nueva invitación a una sala
Comment[eu]=Gela baterako gonbidapen berri bat dago Comment[eu]=Gela baterako gonbidapen berri bat dago
@@ -208,7 +197,6 @@ Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
Comment[pt]=Existe um novo convite para uma sala Comment[pt]=Existe um novo convite para uma sala
Comment[pt_BR]=Existe um novo convite para uma sala Comment[pt_BR]=Existe um novo convite para uma sala
Comment[ru]=Доступно новое приглашение в комнату
Comment[sl]=Tam je novo povabilo v sobo Comment[sl]=Tam je novo povabilo v sobo
Comment[sv]=Det finns en ny inbjudan till ett rum Comment[sv]=Det finns en ny inbjudan till ett rum
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது

View File

@@ -62,9 +62,6 @@
<label>Enable developer tools</label> <label>Enable developer tools</label>
<default>false</default> <default>false</default>
</entry> </entry>
<entry name="LastSaveDirectory" type="String">
<label>Directory last used for saving a file</label>
</entry>
</group> </group>
<group name="Timeline"> <group name="Timeline">
<entry name="ShowAvatarInTimeline" type="bool"> <entry name="ShowAvatarInTimeline" type="bool">
@@ -72,11 +69,7 @@
<default>true</default> <default>true</default>
</entry> </entry>
<entry name="CompactLayout" type="bool"> <entry name="CompactLayout" type="bool">
<label>Use a compact chat layout</label> <label>Use a compact layout</label>
<default>false</default>
</entry>
<entry name="CompactRoomList" type="bool">
<label>Use a compact room list layout</label>
<default>false</default> <default>false</default>
</entry> </entry>
<entry name="ShowRename" type="bool"> <entry name="ShowRename" type="bool">
@@ -87,10 +80,6 @@
<label>Show avatar update events in the timeline</label> <label>Show avatar update events in the timeline</label>
<default>true</default> <default>true</default>
</entry> </entry>
<entry name="ShowDeletedMessages" type="bool">
<label>Show deleted messages in the timeline</label>
<default>true</default>
</entry>
<entry name="ShowLinkPreview" type="bool"> <entry name="ShowLinkPreview" type="bool">
<label>Show preview of the links in the chat messages</label> <label>Show preview of the links in the chat messages</label>
</entry> </entry>
@@ -137,14 +126,6 @@
<label>The port number of the proxy</label> <label>The port number of the proxy</label>
<default>1080</default> <default>1080</default>
</entry> </entry>
<entry name="ProxyUser" type="String">
<label>The user of the proxy</label>
<default></default>
</entry>
<entry name="ProxyPassword" type="Password">
<label>The password of the proxy</label>
<default></default>
</entry>
</group> </group>
</kcfg> </kcfg>

View File

@@ -14,9 +14,9 @@
#include <QMediaPlayer> #include <QMediaPlayer>
#include <qcoro/qcorosignal.h> #include <qcoro/qcorosignal.h>
#include <qcoro/task.h>
#include <connection.h> #include <connection.h>
#include <csapi/directory.h>
#include <csapi/pushrules.h> #include <csapi/pushrules.h>
#include <csapi/redaction.h> #include <csapi/redaction.h>
#include <csapi/report_content.h> #include <csapi/report_content.h>
@@ -31,9 +31,6 @@
#include <events/roompowerlevelsevent.h> #include <events/roompowerlevelsevent.h>
#include <events/simplestateevents.h> #include <events/simplestateevents.h>
#include <jobs/downloadfilejob.h> #include <jobs/downloadfilejob.h>
#ifndef QUOTIENT_07
#include <joinstate.h>
#endif
#include <qt_connection_util.h> #include <qt_connection_util.h>
#include "controller.h" #include "controller.h"
@@ -97,7 +94,6 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
connect(this, &Room::changed, this, [this] { connect(this, &Room::changed, this, [this] {
Q_EMIT canEncryptRoomChanged(); Q_EMIT canEncryptRoomChanged();
}); });
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
} }
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body) void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
@@ -112,8 +108,7 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
} }
auto mime = QMimeDatabase().mimeTypeForUrl(url); auto mime = QMimeDatabase().mimeTypeForUrl(url);
url.setScheme("file"); QFileInfo fileInfo(url.toLocalFile());
QFileInfo fileInfo(url.isLocalFile() ? url.toLocalFile() : url.toString());
EventContent::TypedBase *content; EventContent::TypedBase *content;
if (mime.name().startsWith("image/")) { if (mime.name().startsWith("image/")) {
QImage image(url.toLocalFile()); QImage image(url.toLocalFile());
@@ -333,7 +328,7 @@ QString NeoChatRoom::subtitleText()
{ {
static const QRegularExpression blockquote("(\r\n\t|\n|\r\t|)> "); static const QRegularExpression blockquote("(\r\n\t|\n|\r\t|)> ");
static const QRegularExpression heading("(\r\n\t|\n|\r\t|)\\#{1,6} "); static const QRegularExpression heading("(\r\n\t|\n|\r\t|)\\#{1,6} ");
static const QRegularExpression newlines("(\r\n\t|\n|\r\t|\r\n)"); static const QRegularExpression newlines("(\r\n\t|\n|\r\t)");
static const QRegularExpression bold1("(\\*\\*|__)(?=\\S)([^\\r]*\\S)\\1"); static const QRegularExpression bold1("(\\*\\*|__)(?=\\S)([^\\r]*\\S)\\1");
static const QRegularExpression bold2("(\\*|_)(?=\\S)([^\\r]*\\S)\\1"); static const QRegularExpression bold2("(\\*|_)(?=\\S)([^\\r]*\\S)\\1");
static const QRegularExpression strike1("~~(.*)~~"); static const QRegularExpression strike1("~~(.*)~~");
@@ -478,6 +473,13 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
auto base = url.scheme() + QStringLiteral("://") + url.host() + (url.port() != -1 ? ':' + QString::number(url.port()) : QString()); auto base = url.scheme() + QStringLiteral("://") + url.host() + (url.port() != -1 ? ':' + QString::number(url.port()) : QString());
htmlBody.replace(utils::mxcImageRegExp, QStringLiteral(R"(<img \1 src="%1/_matrix/media/r0/download/\2/\3" \4 > )").arg(base)); htmlBody.replace(utils::mxcImageRegExp, QStringLiteral(R"(<img \1 src="%1/_matrix/media/r0/download/\2/\3" \4 > )").arg(base));
if (e.msgtype() == MessageEventType::Emote) {
auto author = static_cast<NeoChatUser *>(user(e.senderId()));
int firstPara = htmlBody.indexOf("<p>");
htmlBody.insert(firstPara == -1 ? 0 : firstPara + 3,
"* <a href='https://matrix.to/#/" + author->id() + "' style='color: " + author->color().name() + "'>"
+ author->displayname(this) + "</a> ");
}
return htmlBody; return htmlBody;
} }
@@ -499,14 +501,18 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
plainBody = e.plainBody(); plainBody = e.plainBody();
} }
if (prettyPrint) {
if (removeReply) {
plainBody.remove(utils::removeReplyRegex);
}
return Quotient::prettyPrint(plainBody);
}
if (removeReply) { if (removeReply) {
return plainBody.remove(utils::removeReplyRegex); plainBody = plainBody.remove(utils::removeReplyRegex);
}
if (prettyPrint) {
plainBody = Quotient::prettyPrint(plainBody);
}
if (e.msgtype() == MessageEventType::Emote) {
auto author = static_cast<NeoChatUser *>(user(e.senderId()));
plainBody.remove("/me");
plainBody.insert(0,
"* <a href='https://matrix.to/#/" + author->id() + "' style='color: " + author->color().name() + "'>"
+ author->displayname(this) + "</a> ");
} }
return plainBody; return plainBody;
}, },
@@ -604,10 +610,8 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
} else { } else {
return i18n("self-banned from the room"); return i18n("self-banned from the room");
} }
case MembershipType::Knock: { case MembershipType::Knock:
QString reason(e.contentJson()["reason"_ls].toString().toHtmlEscaped()); return i18n("requested an invite");
return reason.isEmpty() ? i18n("requested an invite") : i18n("requested an invite with reason: %1", reason);
}
default:; default:;
} }
return i18n("made something unknown"); return i18n("made something unknown");
@@ -739,25 +743,13 @@ void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, Mess
} }
if (isEdit) { if (isEdit) {
QJsonObject content{{"body", text}, {"msgtype", msgTypeToString(type)}, {"format", "org.matrix.custom.html"}, {"formatted_body", html}};
if (isReply) {
content["m.relates_to"] =
QJsonObject {
{"m.in_reply_to",
QJsonObject {
{"event_id", replyEventId}
}
}
};
}
QJsonObject json{ QJsonObject json{
{"type", "m.room.message"}, {"type", "m.room.message"},
{"msgtype", msgTypeToString(type)}, {"msgtype", msgTypeToString(type)},
{"body", "* " + text}, {"body", "* " + text},
{"format", "org.matrix.custom.html"}, {"format", "org.matrix.custom.html"},
{"formatted_body", html}, {"formatted_body", html},
{"m.new_content", content}, {"m.new_content", QJsonObject{{"body", text}, {"msgtype", msgTypeToString(type)}, {"format", "org.matrix.custom.html"}, {"formatted_body", html}}},
{"m.relates_to", QJsonObject{{"rel_type", "m.replace"}, {"event_id", relateToEventId}}}}; {"m.relates_to", QJsonObject{{"rel_type", "m.replace"}, {"event_id", relateToEventId}}}};
postJson("m.room.message", json); postJson("m.room.message", json);
@@ -958,305 +950,6 @@ void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
// Not emitting historyVisibilityChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value. // Not emitting historyVisibilityChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
} }
void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel)
{
if (joinedCount() <= 1) {
qWarning() << "Cannot modify the power level of the only user";
return;
}
if (!canSendState("m.room.power_levels")) {
qWarning() << "Power level too low to set user power levels";
return;
}
#ifdef QUOTIENT_07
if (!isMember(userID)) {
#else
if (memberJoinState(user(userID)) == JoinState::Join) {
#endif
qWarning() << "User is not a member of this room so power level cannot be set";
return;
}
int clampPowerLevel = std::clamp(powerLevel, 0, 100);
#ifdef QUOTIENT_07
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
#else
auto powerLevelContent = getCurrentState<RoomPowerLevelsEvent>()->contentJson();
#endif
auto powerLevelUserOverrides = powerLevelContent["users"].toObject();
if (powerLevelUserOverrides[userID] != clampPowerLevel) {
powerLevelUserOverrides[userID] = clampPowerLevel;
powerLevelContent["users"] = powerLevelUserOverrides;
#ifdef QUOTIENT_07
setState("m.room.power_levels", "", powerLevelContent);
#else
setState<RoomPowerLevelsEvent>(QJsonObject{{"type", "m.room.power_levels"}, {"state_key", ""}, {"content", powerLevelContent}});
#endif
}
}
int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent) const
{
#ifdef QUOTIENT_07
const auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
#else
const auto powerLevelEvent = getCurrentState<RoomPowerLevelsEvent>();
#endif
if (eventName == "ban") {
return powerLevelEvent->ban();
} else if (eventName == "kick") {
return powerLevelEvent->kick();
} else if (eventName == "invite") {
return powerLevelEvent->invite();
} else if (eventName == "redact") {
return powerLevelEvent->redact();
} else if (eventName == "users_default") {
return powerLevelEvent->usersDefault();
} else if (eventName == "state_default") {
return powerLevelEvent->stateDefault();
} else if (eventName == "events_default") {
return powerLevelEvent->eventsDefault();
} else if (isStateEvent) {
return powerLevelEvent->powerLevelForState(eventName);
} else {
return powerLevelEvent->powerLevelForEvent(eventName);
}
}
void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent)
{
#ifdef QUOTIENT_07
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
#else
auto powerLevelContent = getCurrentState<RoomPowerLevelsEvent>()->contentJson();
#endif
int clampPowerLevel = std::clamp(newPowerLevel, 0, 100);
int powerLevel = 0;
if (powerLevelContent.contains(eventName)) {
powerLevel = powerLevelContent[eventName].toInt();
if (powerLevel != clampPowerLevel) {
powerLevelContent[eventName] = clampPowerLevel;
}
} else {
auto eventPowerLevels = powerLevelContent["events"].toObject();
if (eventPowerLevels.contains(eventName)) {
powerLevel = eventPowerLevels[eventName].toInt();
} else {
if (isStateEvent) {
powerLevel = powerLevelContent["state_default"].toInt();
} else {
powerLevel = powerLevelContent["events_default"].toInt();
}
}
if (powerLevel != clampPowerLevel) {
eventPowerLevels[eventName] = clampPowerLevel;
powerLevelContent["events"] = eventPowerLevels;
}
}
#ifdef QUOTIENT_07
setState("m.room.power_levels", "", powerLevelContent);
#else
setState<RoomPowerLevelsEvent>(QJsonObject{{"type", "m.room.power_levels"}, {"state_key", ""}, {"content", powerLevelContent}});
#endif
}
int NeoChatRoom::defaultUserPowerLevel() const
{
return powerLevel("users_default");
}
void NeoChatRoom::setDefaultUserPowerLevel(const int &newPowerLevel)
{
setPowerLevel("users_default", newPowerLevel);
}
int NeoChatRoom::invitePowerLevel() const
{
return powerLevel("invite");
}
void NeoChatRoom::setInvitePowerLevel(const int &newPowerLevel)
{
setPowerLevel("invite", newPowerLevel);
}
int NeoChatRoom::kickPowerLevel() const
{
return powerLevel("kick");
}
void NeoChatRoom::setKickPowerLevel(const int &newPowerLevel)
{
setPowerLevel("kick", newPowerLevel);
}
int NeoChatRoom::banPowerLevel() const
{
return powerLevel("ban");
}
void NeoChatRoom::setBanPowerLevel(const int &newPowerLevel)
{
setPowerLevel("ban", newPowerLevel);
}
int NeoChatRoom::redactPowerLevel() const
{
return powerLevel("redact");
}
void NeoChatRoom::setRedactPowerLevel(const int &newPowerLevel)
{
setPowerLevel("redact", newPowerLevel);
}
int NeoChatRoom::statePowerLevel() const
{
return powerLevel("state_default");
}
void NeoChatRoom::setStatePowerLevel(const int &newPowerLevel)
{
setPowerLevel("state_default", newPowerLevel);
}
int NeoChatRoom::defaultEventPowerLevel() const
{
return powerLevel("events_default");
}
void NeoChatRoom::setDefaultEventPowerLevel(const int &newPowerLevel)
{
setPowerLevel("events_default", newPowerLevel);
}
int NeoChatRoom::powerLevelPowerLevel() const
{
return powerLevel("m.room.power_levels", true);
}
void NeoChatRoom::setPowerLevelPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.power_levels", newPowerLevel, true);
}
int NeoChatRoom::namePowerLevel() const
{
return powerLevel("m.room.name", true);
}
void NeoChatRoom::setNamePowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.name", newPowerLevel, true);
}
int NeoChatRoom::avatarPowerLevel() const
{
return powerLevel("m.room.avatar", true);
}
void NeoChatRoom::setAvatarPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.avatar", newPowerLevel, true);
}
int NeoChatRoom::canonicalAliasPowerLevel() const
{
return powerLevel("m.room.canonical_alias", true);
}
void NeoChatRoom::setCanonicalAliasPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.canonical_alias", newPowerLevel, true);
}
int NeoChatRoom::topicPowerLevel() const
{
return powerLevel("m.room.topic", true);
}
void NeoChatRoom::setTopicPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.topic", newPowerLevel, true);
}
int NeoChatRoom::encryptionPowerLevel() const
{
return powerLevel("m.room.encryption", true);
}
void NeoChatRoom::setEncryptionPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.encryption", newPowerLevel, true);
}
int NeoChatRoom::historyVisibilityPowerLevel() const
{
return powerLevel("m.room.history_visibility", true);
}
void NeoChatRoom::setHistoryVisibilityPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.history_visibility", newPowerLevel, true);
}
int NeoChatRoom::pinnedEventsPowerLevel() const
{
return powerLevel("m.room.pinned_events", true);
}
void NeoChatRoom::setPinnedEventsPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.pinned_events", newPowerLevel, true);
}
int NeoChatRoom::tombstonePowerLevel() const
{
return powerLevel("m.room.tombstone", true);
}
void NeoChatRoom::setTombstonePowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.tombstone", newPowerLevel, true);
}
int NeoChatRoom::serverAclPowerLevel() const
{
return powerLevel("m.room.server_acl", true);
}
void NeoChatRoom::setServerAclPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.room.server_acl", newPowerLevel, true);
}
int NeoChatRoom::spaceChildPowerLevel() const
{
return powerLevel("m.space.child", true);
}
void NeoChatRoom::setSpaceChildPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.space.child", newPowerLevel, true);
}
int NeoChatRoom::spaceParentPowerLevel() const
{
return powerLevel("m.space.parent", true);
}
void NeoChatRoom::setSpaceParentPowerLevel(const int &newPowerLevel)
{
setPowerLevel("m.space.parent", newPowerLevel, true);
}
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason) QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
{ {
QStringList events; QStringList events;
@@ -1642,57 +1335,3 @@ void NeoChatRoom::download(const QString &eventId, const QUrl &localFilename)
job->start(); job->start();
#endif #endif
} }
void NeoChatRoom::mapAlias(const QString &alias)
{
auto getLocalAliasesJob = connection()->callApi<GetLocalAliasesJob>(id());
connect(getLocalAliasesJob, &BaseJob::success, this, [this, getLocalAliasesJob, alias] {
if (getLocalAliasesJob->aliases().contains(alias)) {
return;
} else {
auto setRoomAliasJob = connection()->callApi<SetRoomAliasJob>(alias, id());
connect(setRoomAliasJob, &BaseJob::success, this, [this, alias] {
auto newAltAliases = altAliases();
newAltAliases.append(alias);
setLocalAliases(newAltAliases);
});
}
});
}
void NeoChatRoom::unmapAlias(const QString &alias)
{
connection()->callApi<DeleteRoomAliasJob>(alias);
}
void NeoChatRoom::setCanonicalAlias(const QString &newAlias)
{
QString oldCanonicalAlias = canonicalAlias();
Room::setCanonicalAlias(newAlias);
connect(this, &Room::namesChanged, this, [this, newAlias, oldCanonicalAlias] {
if (canonicalAlias() == newAlias) {
// If the new canonical alias is already a published alt alias remove it otherwise it will be in both lists.
// The server doesn't prevent this so we need to handle it.
auto newAltAliases = altAliases();
if (!oldCanonicalAlias.isEmpty()) {
newAltAliases.append(oldCanonicalAlias);
}
if (newAltAliases.contains(newAlias)) {
newAltAliases.removeAll(newAlias);
Room::setLocalAliases(newAltAliases);
}
}
});
}
int NeoChatRoom::maxRoomVersion() const
{
int maxVersion = 0;
for (auto roomVersion : connection()->availableRoomVersions()) {
if (roomVersion.id.toInt() > maxVersion) {
maxVersion = roomVersion.id.toInt();
}
}
return maxVersion;
}

View File

@@ -3,7 +3,6 @@
#pragma once #pragma once
#include <qobjectdefs.h>
#include <room.h> #include <room.h>
#include <QCache> #include <QCache>
@@ -47,32 +46,9 @@ class NeoChatRoom : public Quotient::Room
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged) Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged) Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged) Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged) Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
Q_PROPERTY(QString historyVisibility READ historyVisibility WRITE setHistoryVisibility NOTIFY historyVisibilityChanged) Q_PROPERTY(QString historyVisibility READ historyVisibility WRITE setHistoryVisibility NOTIFY historyVisibilityChanged)
// Properties for the various permission levels for the room
Q_PROPERTY(int defaultUserPowerLevel READ defaultUserPowerLevel WRITE setDefaultUserPowerLevel NOTIFY defaultUserPowerLevelChanged)
Q_PROPERTY(int invitePowerLevel READ invitePowerLevel WRITE setInvitePowerLevel NOTIFY invitePowerLevelChanged)
Q_PROPERTY(int kickPowerLevel READ kickPowerLevel WRITE setKickPowerLevel NOTIFY kickPowerLevelChanged)
Q_PROPERTY(int banPowerLevel READ banPowerLevel WRITE setBanPowerLevel NOTIFY banPowerLevelChanged)
Q_PROPERTY(int redactPowerLevel READ redactPowerLevel WRITE setRedactPowerLevel NOTIFY redactPowerLevelChanged)
Q_PROPERTY(int statePowerLevel READ statePowerLevel WRITE setStatePowerLevel NOTIFY statePowerLevelChanged)
Q_PROPERTY(int defaultEventPowerLevel READ defaultEventPowerLevel WRITE setDefaultEventPowerLevel NOTIFY defaultEventPowerLevelChanged)
Q_PROPERTY(int powerLevelPowerLevel READ powerLevelPowerLevel WRITE setPowerLevelPowerLevel NOTIFY powerLevelPowerLevelChanged)
Q_PROPERTY(int namePowerLevel READ namePowerLevel WRITE setNamePowerLevel NOTIFY namePowerLevelChanged)
Q_PROPERTY(int avatarPowerLevel READ avatarPowerLevel WRITE setAvatarPowerLevel NOTIFY avatarPowerLevelChanged)
Q_PROPERTY(int canonicalAliasPowerLevel READ canonicalAliasPowerLevel WRITE setCanonicalAliasPowerLevel NOTIFY canonicalAliasPowerLevelChanged)
Q_PROPERTY(int topicPowerLevel READ topicPowerLevel WRITE setTopicPowerLevel NOTIFY topicPowerLevelChanged)
Q_PROPERTY(int encryptionPowerLevel READ encryptionPowerLevel WRITE setEncryptionPowerLevel NOTIFY encryptionPowerLevelChanged)
Q_PROPERTY(int historyVisibilityPowerLevel READ historyVisibilityPowerLevel WRITE setHistoryVisibilityPowerLevel NOTIFY historyVisibilityPowerLevelChanged)
Q_PROPERTY(int pinnedEventsPowerLevel READ pinnedEventsPowerLevel WRITE setPinnedEventsPowerLevel NOTIFY pinnedEventsPowerLevelChanged)
Q_PROPERTY(int tombstonePowerLevel READ tombstonePowerLevel WRITE setTombstonePowerLevel NOTIFY tombstonePowerLevelChanged)
Q_PROPERTY(int serverAclPowerLevel READ serverAclPowerLevel WRITE setServerAclPowerLevel NOTIFY serverAclPowerLevelChanged)
Q_PROPERTY(int spaceChildPowerLevel READ spaceChildPowerLevel WRITE setSpaceChildPowerLevel NOTIFY spaceChildPowerLevelChanged)
Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged)
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged) Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
Q_PROPERTY(PushNotificationState::State pushNotificationState MEMBER m_currentPushNotificationState WRITE setPushNotificationState NOTIFY Q_PROPERTY(PushNotificationState::State pushNotificationState MEMBER m_currentPushNotificationState WRITE setPushNotificationState NOTIFY
pushNotificationStateChanged) pushNotificationStateChanged)
@@ -88,13 +64,6 @@ class NeoChatRoom : public Quotient::Room
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged) Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged) Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged)
/**
* @brief Get the maximum room version that the server supports.
*
* Only returns main integer room versions (i.e. no msc room versions).
*/
Q_PROPERTY(int maxRoomVersion READ maxRoomVersion NOTIFY maxRoomVersionChanged)
public: public:
enum MessageType { enum MessageType {
Positive, Positive,
@@ -147,68 +116,6 @@ public:
[[nodiscard]] QString historyVisibility() const; [[nodiscard]] QString historyVisibility() const;
void setHistoryVisibility(const QString &historyVisibilityRule); void setHistoryVisibility(const QString &historyVisibilityRule);
Q_INVOKABLE void setUserPowerLevel(const QString &userID, const int &powerLevel);
[[nodiscard]] int powerLevel(const QString &eventName, const bool &isStateEvent = false) const;
void setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent = false);
[[nodiscard]] int defaultUserPowerLevel() const;
void setDefaultUserPowerLevel(const int &newPowerLevel);
[[nodiscard]] int invitePowerLevel() const;
void setInvitePowerLevel(const int &newPowerLevel);
[[nodiscard]] int kickPowerLevel() const;
void setKickPowerLevel(const int &newPowerLevel);
[[nodiscard]] int banPowerLevel() const;
void setBanPowerLevel(const int &newPowerLevel);
[[nodiscard]] int redactPowerLevel() const;
void setRedactPowerLevel(const int &newPowerLevel);
[[nodiscard]] int statePowerLevel() const;
void setStatePowerLevel(const int &newPowerLevel);
[[nodiscard]] int defaultEventPowerLevel() const;
void setDefaultEventPowerLevel(const int &newPowerLevel);
[[nodiscard]] int powerLevelPowerLevel() const;
void setPowerLevelPowerLevel(const int &newPowerLevel);
[[nodiscard]] int namePowerLevel() const;
void setNamePowerLevel(const int &newPowerLevel);
[[nodiscard]] int avatarPowerLevel() const;
void setAvatarPowerLevel(const int &newPowerLevel);
[[nodiscard]] int canonicalAliasPowerLevel() const;
void setCanonicalAliasPowerLevel(const int &newPowerLevel);
[[nodiscard]] int topicPowerLevel() const;
void setTopicPowerLevel(const int &newPowerLevel);
[[nodiscard]] int encryptionPowerLevel() const;
void setEncryptionPowerLevel(const int &newPowerLevel);
[[nodiscard]] int historyVisibilityPowerLevel() const;
void setHistoryVisibilityPowerLevel(const int &newPowerLevel);
[[nodiscard]] int pinnedEventsPowerLevel() const;
void setPinnedEventsPowerLevel(const int &newPowerLevel);
[[nodiscard]] int tombstonePowerLevel() const;
void setTombstonePowerLevel(const int &newPowerLevel);
[[nodiscard]] int serverAclPowerLevel() const;
void setServerAclPowerLevel(const int &newPowerLevel);
[[nodiscard]] int spaceChildPowerLevel() const;
void setSpaceChildPowerLevel(const int &newPowerLevel);
[[nodiscard]] int spaceParentPowerLevel() const;
void setSpaceParentPowerLevel(const int &newPowerLevel);
[[nodiscard]] bool hasFileUploading() const [[nodiscard]] bool hasFileUploading() const
{ {
return m_hasFileUploading; return m_hasFileUploading;
@@ -294,16 +201,6 @@ public:
Q_INVOKABLE bool downloadTempFile(const QString &eventId); Q_INVOKABLE bool downloadTempFile(const QString &eventId);
/*
* Map an alias to the room
*
* Note: this is different to setLocalAliases as that can only
* get the room to publish and alias that is already mapped.
*/
Q_INVOKABLE void mapAlias(const QString &alias);
Q_INVOKABLE void unmapAlias(const QString &alias);
Q_INVOKABLE void setCanonicalAlias(const QString &newAlias);
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
Q_INVOKABLE PollHandler *poll(const QString &eventId); Q_INVOKABLE PollHandler *poll(const QString &eventId);
#endif #endif
@@ -315,8 +212,6 @@ public:
} }
#endif #endif
int maxRoomVersion() const;
private: private:
QSet<const Quotient::RoomEvent *> highlights; QSet<const Quotient::RoomEvent *> highlights;
@@ -368,26 +263,6 @@ Q_SIGNALS:
void canEncryptRoomChanged(); void canEncryptRoomChanged();
void joinRuleChanged(); void joinRuleChanged();
void historyVisibilityChanged(); void historyVisibilityChanged();
void maxRoomVersionChanged();
void defaultUserPowerLevelChanged();
void invitePowerLevelChanged();
void kickPowerLevelChanged();
void banPowerLevelChanged();
void redactPowerLevelChanged();
void statePowerLevelChanged();
void defaultEventPowerLevelChanged();
void powerLevelPowerLevelChanged();
void namePowerLevelChanged();
void avatarPowerLevelChanged();
void canonicalAliasPowerLevelChanged();
void topicPowerLevelChanged();
void encryptionPowerLevelChanged();
void historyVisibilityPowerLevelChanged();
void pinnedEventsPowerLevelChanged();
void tombstonePowerLevelChanged();
void serverAclPowerLevelChanged();
void spaceChildPowerLevelChanged();
void spaceParentPowerLevelChanged();
public Q_SLOTS: public Q_SLOTS:
void uploadFile(const QUrl &url, const QString &body = QString()); void uploadFile(const QUrl &url, const QString &body = QString());

View File

@@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include <QImage>
#include <QJsonArray> #include <QJsonArray>
#include <KLocalizedString> #include <KLocalizedString>

View File

@@ -8,7 +8,6 @@ Name[ca]=NeoChat
Name[ca@valencia]=NeoChat Name[ca@valencia]=NeoChat
Name[cs]=NeoChat Name[cs]=NeoChat
Name[de]=NeoChat Name[de]=NeoChat
Name[el]=NeoChat
Name[en_GB]=NeoChat Name[en_GB]=NeoChat
Name[es]=NeoChat Name[es]=NeoChat
Name[eu]=NeoChat Name[eu]=NeoChat
@@ -29,7 +28,6 @@ Name[pl]=NeoChat
Name[pt]=NeoChat Name[pt]=NeoChat
Name[pt_BR]=NeoChat Name[pt_BR]=NeoChat
Name[ro]=NeoChat Name[ro]=NeoChat
Name[ru]=NeoChat
Name[sk]=NeoChat Name[sk]=NeoChat
Name[sl]=NeoChat Name[sl]=NeoChat
Name[sv]=NeoChat Name[sv]=NeoChat
@@ -43,8 +41,6 @@ Comment[ar]=اعثر على غرف في نيوتشات
Comment[az]=NeoChat-da otaqları tapın Comment[az]=NeoChat-da otaqları tapın
Comment[ca]=Cerca sales en el NeoChat Comment[ca]=Cerca sales en el NeoChat
Comment[ca@valencia]=Busca sales en NeoChat Comment[ca@valencia]=Busca sales en NeoChat
Comment[de]=Räume in NeoChat finden
Comment[el]=Εύρεση αιθουσών στο NeoChat
Comment[en_GB]=Find rooms in NeoChat Comment[en_GB]=Find rooms in NeoChat
Comment[es]=Buscar salas en NeoChat Comment[es]=Buscar salas en NeoChat
Comment[eu]=Bilatu gelak NeoChat-en Comment[eu]=Bilatu gelak NeoChat-en
@@ -60,7 +56,6 @@ Comment[nl]=Rooms zoeken in NeoChat
Comment[pl]=Znajdź pokoje w NeoChat Comment[pl]=Znajdź pokoje w NeoChat
Comment[pt]=Procurar salas no NeoChat Comment[pt]=Procurar salas no NeoChat
Comment[pt_BR]=Encontrar salas no NeoChat Comment[pt_BR]=Encontrar salas no NeoChat
Comment[ru]=Поиск комнаты NeoChat
Comment[sl]=Najdi sobe v NeoChatu Comment[sl]=Najdi sobe v NeoChatu
Comment[sv]=Sök efter rum i NeoChat Comment[sv]=Sök efter rum i NeoChat
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும் Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்

View File

@@ -14,8 +14,10 @@ QQC2.ToolBar {
id: chatBar id: chatBar
property alias inputFieldText: inputField.text property alias inputFieldText: inputField.text
property alias textField: inputField property alias textField: inputField
property alias emojiPaneOpened: emojiButton.checked
property alias cursorPosition: inputField.cursorPosition property alias cursorPosition: inputField.cursorPosition
signal closeAllTriggered()
signal inputFieldForceActiveFocusTriggered() signal inputFieldForceActiveFocusTriggered()
signal messageSent() signal messageSent()
@@ -129,18 +131,12 @@ QQC2.ToolBar {
} else if (event.key === Qt.Key_Up && inputField.text.length === 0) { } else if (event.key === Qt.Key_Up && inputField.text.length === 0) {
let editEvent = messageEventModel.getLastLocalUserMessageEventId() let editEvent = messageEventModel.getLastLocalUserMessageEventId()
if (editEvent) { if (editEvent) {
if(editEvent["m.relates_to"]) {
currentRoom.chatBoxReplyId = editEvent["m.relates_to"]["m.in_reply_to"]["event_id"];
}
currentRoom.chatBoxEditId = editEvent["event_id"] currentRoom.chatBoxEditId = editEvent["event_id"]
} }
} else if (event.key === Qt.Key_Up && completionMenu.visible) { } else if (event.key === Qt.Key_Up && completionMenu.visible) {
completionMenu.decrementIndex() completionMenu.decrementIndex()
} else if (event.key === Qt.Key_Down && completionMenu.visible) { } else if (event.key === Qt.Key_Down && completionMenu.visible) {
completionMenu.incrementIndex() completionMenu.incrementIndex()
} else if (event.key === Qt.Key_Backspace && inputField.text.length <= 1) {
currentRoom.sendTypingNotification(false)
repeatTimer.stop()
} }
} }
@@ -151,10 +147,10 @@ QQC2.ToolBar {
onTextChanged: { onTextChanged: {
if (!repeatTimer.running && Config.typingNotifications) { if (!repeatTimer.running && Config.typingNotifications) {
var textExists = text.length > 0 currentRoom.sendTypingNotification(true)
currentRoom.sendTypingNotification(textExists)
textExists ? repeatTimer.start() : repeatTimer.stop()
} }
repeatTimer.start()
currentRoom.chatBoxText = text currentRoom.chatBoxText = text
} }
} }
@@ -206,14 +202,6 @@ QQC2.ToolBar {
display: QQC2.AbstractButton.IconOnly display: QQC2.AbstractButton.IconOnly
checkable: true checkable: true
onClicked: {
if (emojiDialog.visible) {
emojiDialog.close()
} else {
emojiDialog.open()
}
}
QQC2.ToolTip.text: text QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
} }
@@ -233,19 +221,6 @@ QQC2.ToolBar {
} }
} }
EmojiDialog {
id: emojiDialog
x: parent.width - implicitWidth
y: -implicitHeight - Kirigami.Units.smallSpacing
modal: false
includeCustom: true
closeOnChosen: false
onChosen: insertText(emoji)
onClosed: if (emojiButton.checked) emojiButton.checked = false
}
CompletionMenu { CompletionMenu {
id: completionMenu id: completionMenu
height: implicitHeight height: implicitHeight
@@ -292,7 +267,7 @@ QQC2.ToolBar {
function postMessage() { function postMessage() {
actionsHandler.handleMessage(); actionsHandler.handleMessage();
repeatTimer.stop()
currentRoom.markAllMessagesAsRead(); currentRoom.markAllMessagesAsRead();
inputField.clear(); inputField.clear();
currentRoom.chatBoxReplyId = ""; currentRoom.chatBoxReplyId = "";

View File

@@ -46,6 +46,32 @@ ColumnLayout {
} }
} }
Kirigami.Separator {
id: emojiPickerLoaderSeparator
visible: emojiPickerLoader.visible
Layout.fillWidth: true
height: visible ? implicitHeight : 0
}
Loader {
id: emojiPickerLoader
active: visible
visible: chatBar.emojiPaneOpened
Layout.fillWidth: true
sourceComponent: QQC2.Pane {
topPadding: 0
bottomPadding: 0
rightPadding: 0
leftPadding: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View
contentItem: EmojiPicker {
textArea: chatBar.textField
onChosen: insertText(emoji)
}
}
}
Kirigami.Separator { Kirigami.Separator {
id: replySeparator id: replySeparator
visible: replyPane.visible visible: replyPane.visible
@@ -87,7 +113,9 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
onCloseAllTriggered: closeAll()
onMessageSent: { onMessageSent: {
closeAll()
chatBox.messageSent(); chatBox.messageSent();
} }
@@ -109,4 +137,9 @@ ColumnLayout {
function focusInputField() { function focusInputField() {
chatBar.inputFieldForceActiveFocusTriggered() chatBar.inputFieldForceActiveFocusTriggered()
} }
function closeAll() {
// TODO clear();
chatBar.emojiPaneOpened = false;
}
} }

View File

@@ -1,59 +0,0 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import org.kde.kirigami 2.20 as Kirigami
QQC2.ItemDelegate {
id: emojiDelegate
property string name
property string emoji
property bool showTones: false
QQC2.ToolTip.text: emojiDelegate.name
QQC2.ToolTip.visible: hovered && emojiDelegate.name !== ""
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
leftInset: Kirigami.Units.smallSpacing
topInset: Kirigami.Units.smallSpacing
rightInset: Kirigami.Units.smallSpacing
bottomInset: Kirigami.Units.smallSpacing
contentItem: Item {
Kirigami.Heading {
anchors.fill: parent
visible: !emojiDelegate.emoji.startsWith("image")
text: emojiDelegate.emoji
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: "emoji"
Kirigami.Icon {
width: Kirigami.Units.gridUnit * 0.5
height: Kirigami.Units.gridUnit * 0.5
source: "arrow-down"
anchors.bottom: parent.bottom
anchors.right: parent.right
visible: emojiDelegate.showTones
}
}
Image {
anchors.fill: parent
visible: emojiDelegate.emoji.startsWith("image")
source: visible ? emojiDelegate.emoji : ""
}
}
background: Rectangle {
color: emojiDelegate.checked ? Kirigami.Theme.highlightColor : Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing
Rectangle {
radius: Kirigami.Units.smallSpacing
anchors.fill: parent
color: Kirigami.Theme.highlightColor
opacity: emojiDelegate.hovered && !emojiDelegate.pressed ? 0.2 : 0
}
}
}

View File

@@ -1,87 +0,0 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import org.kde.kirigami 2.20 as Kirigami
import org.kde.neochat 1.0
QQC2.ScrollView {
id: emojiGrid
property alias model: emojis.model
property alias count: emojis.count
required property int targetIconSize
readonly property int emojisPerRow: emojis.width / targetIconSize
required property bool withCustom
readonly property var searchCategory: withCustom ? EmojiModel.Search : EmojiModel.SearchNoCustom
required property QtObject header
signal chosen(string unicode)
onActiveFocusChanged: if (activeFocus) {
emojis.forceActiveFocus()
}
GridView {
id: emojis
anchors.fill: parent
anchors.rightMargin: parent.QQC2.ScrollBar.vertical.visible ? parent.QQC2.ScrollBar.vertical.width : 0
currentIndex: -1
keyNavigationEnabled: true
onActiveFocusChanged: if (activeFocus && currentIndex === -1) {
currentIndex = 0
} else {
currentIndex = -1
}
onModelChanged: currentIndex = -1
cellWidth: emojis.width / emojiGrid.emojisPerRow
cellHeight: emojiGrid.targetIconSize
KeyNavigation.up: emojiGrid.header
clip: true
delegate: EmojiDelegate {
id: emojiDelegate
checked: emojis.currentIndex === model.index
emoji: modelData.unicode
name: modelData.shortName
width: emojis.cellWidth
height: emojis.cellHeight
Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked()
onClicked: {
emojiGrid.chosen(modelData.isCustom ? modelData.shortName : modelData.unicode)
EmojiModel.emojiUsed(modelData)
}
Keys.onSpacePressed: pressAndHold()
onPressAndHold: {
if (EmojiModel.tones(modelData.shortName).length === 0) {
return;
}
let tones = tonesPopupComponent.createObject(emojiDelegate, {shortName: modelData.shortName, unicode: modelData.unicode, categoryIconSize: emojiGrid.targetIconSize})
tones.open()
tones.forceActiveFocus()
}
showTones: EmojiModel.tones(modelData.shortName).length > 0
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
text: i18n("No emojis")
visible: emojis.count === 0
}
}
Component {
id: tonesPopupComponent
EmojiTonesPicker {
onChosen: emojiGrid.chosen(emoji)
}
}
}

View File

@@ -1,135 +1,123 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de> // SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2 import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
ColumnLayout { ColumnLayout {
id: root id: _picker
property bool includeCustom: false property var emojiCategory: EmojiModel.History
property bool showQuickReaction: false property var textArea
readonly property var emojiModel: EmojiModel
readonly property var currentEmojiModel: {
if (includeCustom) {
EmojiModel.categoriesWithCustom
} else {
EmojiModel.categories
}
}
readonly property int categoryIconSize: Math.round(Kirigami.Units.gridUnit * 2.5)
readonly property var currentCategory: currentEmojiModel[categories.currentIndex].category
readonly property alias categoryCount: categories.count
signal chosen(string emoji) signal chosen(string emoji)
onActiveFocusChanged: if (activeFocus) {
searchField.forceActiveFocus()
}
spacing: 0 spacing: 0
QQC2.ScrollView { QQC2.ScrollView {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height Layout.preferredHeight: Kirigami.Units.gridUnit * 2 + QQC2.ScrollBar.horizontal.height + 2 // for the focus line
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0 QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
ListView { ListView {
id: categories
clip: true clip: true
focus: true
orientation: ListView.Horizontal orientation: ListView.Horizontal
Keys.onReturnPressed: if (emojiGrid.count > 0) emojiGrid.focus = true model: EmojiModel.categories
Keys.onEnterPressed: if (emojiGrid.count > 0) emojiGrid.focus = true delegate: QQC2.ItemDelegate {
KeyNavigation.down: emojiGrid.count > 0 ? emojiGrid : categories id: del
KeyNavigation.tab: emojiGrid.count > 0 ? emojiGrid : categories
keyNavigationEnabled: true width: contentItem.Layout.preferredWidth
keyNavigationWraps: true height: Kirigami.Units.gridUnit * 2
Keys.forwardTo: searchField
interactive: width !== contentWidth
model: root.currentEmojiModel contentItem: Kirigami.Heading {
Component.onCompleted: categories.forceActiveFocus() horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
level: modelData.category === EmojiModel.Custom ? 4 : 1
delegate: EmojiDelegate { Layout.preferredWidth: modelData.category === EmojiModel.Custom ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
width: root.categoryIconSize
height: width
checked: categories.currentIndex === model.index font.family: modelData.category === EmojiModel.Custom ? Kirigami.Theme.defaultFont.family : 'emoji'
emoji: modelData.emoji text: modelData.category === EmojiModel.Custom ? i18n("Custom") : modelData.emoji
name: modelData.name }
Rectangle {
anchors.bottom: parent.bottom
width: parent.width
height: 2
visible: _picker.emojiCategory === modelData.category
color: Kirigami.Theme.focusColor
}
onClicked: _picker.emojiCategory = modelData.category
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
Layout.preferredHeight: 1
}
QQC2.ScrollView {
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 8
Layout.fillHeight: true
GridView {
cellWidth: Kirigami.Units.gridUnit * 2
cellHeight: Kirigami.Units.gridUnit * 2
clip: true
model: _picker.emojiCategory === EmojiModel.Custom ? CustomEmojiModel : EmojiModel.emojis(_picker.emojiCategory)
delegate: QQC2.ItemDelegate {
width: Kirigami.Units.gridUnit * 2
height: Kirigami.Units.gridUnit * 2
contentItem: Kirigami.Heading {
level: 1
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: 'emoji'
text: modelData.isCustom ? "" : modelData.unicode
}
Image {
visible: modelData.isCustom
source: visible ? modelData.unicode : ""
anchors.fill: parent
anchors.margins: 2
sourceSize.width: width
sourceSize.height: height
Rectangle {
anchors.fill: parent
visible: parent.status === Image.Loading
radius: height/2
gradient: ShimmerGradient { }
}
}
onClicked: { onClicked: {
categories.currentIndex = index if (modelData.isCustom) {
categories.focus = true chosen(modelData.shortName)
} else {
chosen(modelData.unicode)
}
emojiModel.emojiUsed(modelData)
} }
} }
} }
} }
Kirigami.Separator {
Layout.fillWidth: true
Layout.preferredHeight: 1
}
Kirigami.SearchField {
id: searchField
Layout.margins: Kirigami.Units.smallSpacing
Layout.fillWidth: true
/**
* The focus is manged by the parent and we don't want to use the standard
* shortcut as it could block other SearchFields from using it.
*/
focusSequence: ""
}
EmojiGrid {
id: emojiGrid
targetIconSize: root.currentCategory === EmojiModel.Custom ? Kirigami.Units.gridUnit * 3 : root.categoryIconSize // Custom emojis are bigger
model: searchField.text.length === 0 ? EmojiModel.emojis(root.currentCategory) : (root.includeCustom ? EmojiModel.filterModel(searchField.text, false) : EmojiModel.filterModelNoCustom(searchField.text, false))
Layout.fillWidth: true
Layout.fillHeight: true
withCustom: root.includeCustom
onChosen: root.chosen(unicode)
header: categories
Keys.forwardTo: searchField
}
Kirigami.Separator {
visible: showQuickReaction
Layout.fillWidth: true
Layout.preferredHeight: 1
}
QQC2.ScrollView {
visible: showQuickReaction
Layout.fillWidth: true
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
ListView {
id: quickReactions
Layout.fillWidth: true
model: ["👍", "👎", "😄", "🎉", "😕", "❤", "🚀", "👀"]
delegate: EmojiDelegate {
emoji: modelData
height: root.categoryIconSize
width: height
onClicked: root.chosen(modelData)
}
orientation: Qt.Horizontal
}
}
} }

View File

@@ -1,66 +0,0 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import org.kde.kirigami 2.20 as Kirigami
import org.kde.neochat 1.0
QQC2.Popup {
id: tones
signal chosen(string emoji)
Component.onCompleted: {
tonesList.currentIndex = 0;
tonesList.forceActiveFocus();
}
required property string shortName
required property string unicode
required property int categoryIconSize
width: tones.categoryIconSize * tonesList.count + 2 * padding
height: tones.categoryIconSize + 2 * padding
y: -height
padding: 2
modal: true
dim: true
onOpened: x = Math.min(parent.mapFromGlobal(QQC2.Overlay.overlay.width - tones.width, 0).x, -(width - parent.width) / 2)
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: 1
}
ListView {
id: tonesList
width: parent.width
height: parent.height
orientation: Qt.Horizontal
model: EmojiModel.tones(tones.shortName)
keyNavigationEnabled: true
keyNavigationWraps: true
delegate: EmojiDelegate {
id: emojiDelegate
checked: tonesList.currentIndex === model.index
emoji: modelData.unicode
name: modelData.shortName
width: tones.categoryIconSize
height: width
Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked()
onClicked: {
tones.chosen(modelData.unicode)
EmojiModel.emojiUsed(modelData)
tones.close()
}
}
}
}

View File

@@ -26,6 +26,8 @@ QQC2.Popup {
padding: 0 padding: 0
background: null background: null
Keys.onBackPressed: root.close()
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
@@ -291,10 +293,8 @@ QQC2.Popup {
id: saveAsDialog id: saveAsDialog
FileDialog { FileDialog {
fileMode: FileDialog.SaveFile fileMode: FileDialog.SaveFile
folder: Config.lastSaveDirectory.length > 0 ? Config.lastSaveDirectory : StandardPaths.writableLocation(StandardPaths.DownloadLocation) folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: { onAccepted: {
Config.lastSaveDirectory = folder
Config.save()
if (!currentFile) { if (!currentFile) {
return; return;
} }

View File

@@ -5,15 +5,23 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2 import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.kirigami 2.20 as Kirigami import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
Kirigami.LoadingPlaceholder { Kirigami.PlaceholderMessage {
property var showContinueButton: false property var showContinueButton: false
property var showBackButton: false property var showBackButton: false
property string title: i18n("Loading…")
anchors.centerIn: parent
QQC2.Label { QQC2.Label {
text: i18n("Please wait. This might take a little while.") text: i18n("Please wait. This might take a little while.")
} }
QQC2.BusyIndicator {
Layout.alignment: Qt.AlignHCenter
running: false
}
} }

View File

@@ -20,9 +20,6 @@ LoginStep {
target: LoginHelper target: LoginHelper
function onSsoUrlChanged() { function onSsoUrlChanged() {
UrlHelper.openUrl(LoginHelper.ssoUrl) UrlHelper.openUrl(LoginHelper.ssoUrl)
root.showMessage(i18n("Complete the authentication steps in your browser"))
loginButton.enabled = true
loginButton.text = i18n("Login")
} }
function onConnected() { function onConnected() {
processed("qrc:/Loading.qml") processed("qrc:/Loading.qml")
@@ -37,12 +34,10 @@ LoginStep {
} }
} }
QQC2.Button { QQC2.Button {
id: loginButton
text: i18n("Login") text: i18n("Login")
onClicked: { onClicked: {
LoginHelper.loginWithSso() LoginHelper.loginWithSso()
loginButton.enabled = false root.showMessage(i18n("Complete the authentication steps in your browser"))
loginButton.text = i18n("Loading…")
} }
Component.onCompleted: forceActiveFocus() Component.onCompleted: forceActiveFocus()
Keys.onReturnPressed: clicked() Keys.onReturnPressed: clicked()

View File

@@ -40,11 +40,6 @@ QQC2.Popup {
// we used to be able to expect that the text field wouldn't attempt to // we used to be able to expect that the text field wouldn't attempt to
// perform a mini-DDOS attack using signals. // perform a mini-DDOS attack using signals.
autoAccept: false autoAccept: false
/**
* The focus is manged by the popup and we don't want to use the standard
* shortcut as it could block other SearchFields from using it.
*/
focusSequence: ""
Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen
Keys.onLeftPressed: cView.decrementCurrentIndex() Keys.onLeftPressed: cView.decrementCurrentIndex()

View File

@@ -33,7 +33,10 @@ TimelineContainer {
} }
innerObject: RowLayout { innerObject: RowLayout {
Layout.maximumWidth: Math.min(fileDelegate.contentMaxWidth, implicitWidth)
Layout.fillWidth: true
Layout.maximumWidth: fileDelegate.contentMaxWidth
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
@@ -108,16 +111,24 @@ TimelineContainer {
} }
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
spacing: 0 spacing: 0
QQC2.Label { QQC2.Label {
text: model.display text: model.display
wrapMode: Text.Wrap wrapMode: Text.Wrap
Layout.fillWidth: true
} }
QQC2.Label { QQC2.Label {
id: sizeLabel id: sizeLabel
text: Controller.formatByteSize(content.info ? content.info.size : 0) text: Controller.formatByteSize(content.info ? content.info.size : 0)
opacity: 0.7 opacity: 0.7
Layout.fillWidth: true
} }
} }
@@ -148,15 +159,11 @@ TimelineContainer {
FileDialog { FileDialog {
fileMode: FileDialog.SaveFile fileMode: FileDialog.SaveFile
folder: Config.lastSaveDirectory.length > 0 ? Config.lastSaveDirectory : StandardPaths.writableLocation(StandardPaths.DownloadLocation) folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: { onAccepted: if (autoOpenFile) {
Config.lastSaveDirectory = folder UrlHelper.copyTo(progressInfo.localPath, file)
Config.save() } else {
if (autoOpenFile) { currentRoom.download(eventId, file);
UrlHelper.copyTo(progressInfo.localPath, file)
} else {
currentRoom.download(eventId, file);
}
} }
} }
} }

View File

@@ -31,30 +31,10 @@ TimelineContainer {
innerObject: AnimatedImage { innerObject: AnimatedImage {
id: img id: img
property var imageWidth: {
if (imageDelegate.info.w > 0) {
return imageDelegate.info.w;
} else if (sourceSize.width > 0) {
return sourceSize.width;
} else {
return imageDelegate.contentMaxWidth;
}
}
property var imageHeight: {
if (imageDelegate.info.h > 0) {
return imageDelegate.info.h;
} else if (sourceSize.height > 0) {
return sourceSize.height;
} else {
// Default to a 16:9 placeholder
return imageDelegate.contentMaxWidth / 16 * 9;
}
}
Layout.maximumWidth: Math.min(imageDelegate.contentMaxWidth, imageDelegate.maxWidth) Layout.maximumWidth: Math.min(imageDelegate.contentMaxWidth, imageDelegate.maxWidth)
Layout.maximumHeight: Math.min(imageDelegate.contentMaxWidth / imageWidth * imageHeight, imageDelegate.maxWidth / imageWidth * imageHeight) Layout.maximumHeight: Math.min(imageDelegate.contentMaxWidth / sourceSize.width * sourceSize.height, imageDelegate.maxWidth / sourceSize.width * sourceSize.height)
Layout.preferredWidth: imageWidth Layout.preferredWidth: imageDelegate.info.w > 0 ? imageDelegate.info.w : sourceSize.width
Layout.preferredHeight: imageHeight Layout.preferredHeight: imageDelegate.info.h > 0 ? imageDelegate.info.h : sourceSize.height
source: model.mediaUrl source: model.mediaUrl
Image { Image {

View File

@@ -82,12 +82,12 @@ Item {
} }
} }
} }
HoverHandler { }
cursorShape: Qt.PointingHandCursor
} MouseArea {
TapHandler { anchors.fill: parent
acceptedButtons: Qt.LeftButton onClicked: {
onTapped: replyComponent.replyClicked() replyComponent.replyClicked()
} }
} }
@@ -97,16 +97,6 @@ Item {
textMessage: reply.display textMessage: reply.display
textFormat: Text.RichText textFormat: Text.RichText
isReplyLabel: true isReplyLabel: true
HoverHandler {
enabled: !hoveredLink
cursorShape: Qt.PointingHandCursor
}
TapHandler {
enabled: !hoveredLink
acceptedButtons: Qt.LeftButton
onTapped: replyComponent.replyClicked()
}
} }
} }
Component { Component {

View File

@@ -75,7 +75,7 @@ a{
background: " + Kirigami.Theme.textColor + "; background: " + Kirigami.Theme.textColor + ";
} }
" : "") + " " : "") + "
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited && !contentLabel.isReplyLabel ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "") </style>" + textMessage + (isEdited && !contentLabel.isReplyLabel ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
color: Kirigami.Theme.textColor color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor selectedTextColor: Kirigami.Theme.highlightedTextColor

View File

@@ -88,7 +88,6 @@ QQC2.Control {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open() onClicked: userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
} }
} }

View File

@@ -20,7 +20,7 @@ ColumnLayout {
default property alias innerObject : column.children default property alias innerObject : column.children
property Item hoverComponent: hoverActions ?? null property Item hoverComponent: hoverActions
property bool isEmote: false property bool isEmote: false
property bool cardBackground: true property bool cardBackground: true
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout
@@ -106,9 +106,6 @@ ColumnLayout {
// Show hover actions by updating the global hover component to this delegate // Show hover actions by updating the global hover component to this delegate
function updateHoverComponent() { function updateHoverComponent() {
if (!hoverComponent) {
return;
}
if (hovered && !Kirigami.Settings.isMobile) { if (hovered && !Kirigami.Settings.isMobile) {
hoverComponent.delegate = root hoverComponent.delegate = root
hoverComponent.bubble = bubble hoverComponent.bubble = bubble
@@ -232,10 +229,10 @@ ColumnLayout {
QQC2.Label { QQC2.Label {
id: timeLabel id: timeLabel
text: visible ? model.time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : "" text: visible ? time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
color: Kirigami.Theme.disabledTextColor color: Kirigami.Theme.disabledTextColor
QQC2.ToolTip.visible: hoverHandler.hovered QQC2.ToolTip.visible: hoverHandler.hovered
QQC2.ToolTip.text: model.time.toLocaleString(Qt.locale(), Locale.LongFormat) QQC2.ToolTip.text: time.toLocaleString(Qt.locale(), Locale.LongFormat)
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
HoverHandler { HoverHandler {
@@ -271,7 +268,6 @@ ColumnLayout {
id: bubbleBackground id: bubbleBackground
visible: cardBackground && !Config.compactLayout visible: cardBackground && !Config.compactLayout
anchors.fill: parent anchors.fill: parent
Kirigami.Theme.colorSet: Kirigami.Theme.View
color: { color: {
if (model.author.isLocalUser) { if (model.author.isLocalUser) {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15) return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)

View File

@@ -9,54 +9,29 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0 import org.kde.neochat 1.0
QQC2.Popup { QQC2.Popup {
id: emojiPopup id: root
property bool includeCustom: false signal react(string emoji)
property bool closeOnChosen: true
property bool showQuickReaction: false
signal chosen(string emoji)
Connections {
target: RoomManager
function onCurrentRoomChanged() {
emojiPopup.close()
}
}
onVisibleChanged: {
if (!visible) {
return
}
emojiPicker.forceActiveFocus()
}
background: Kirigami.ShadowedRectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing
shadow.size: Kirigami.Units.smallSpacing
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
border.width: 2
}
modal: true modal: true
focus: true focus: true
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
margins: 0 margins: 0
padding: 2 padding: 1
implicitWidth: Kirigami.Units.gridUnit * 16
implicitHeight: Kirigami.Units.gridUnit * 20
background: Rectangle {
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
color: Kirigami.Theme.backgroundColor
border.width: 1
border.color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor,
Kirigami.Theme.textColor,
0.15)
}
implicitHeight: Kirigami.Units.gridUnit * 20 + 2 * padding
width: Math.min(contentItem.categoryIconSize * contentItem.categoryCount + 2 * padding, QQC2.Overlay.overlay.width)
contentItem: EmojiPicker { contentItem: EmojiPicker {
id: emojiPicker onChosen: react(emoji)
height: 400
includeCustom: emojiPopup.includeCustom
showQuickReaction: emojiPopup.showQuickReaction
onChosen: {
emojiPopup.chosen(emoji)
if (emojiPopup.closeOnChosen) emojiPopup.close()
}
} }
} }

View File

@@ -24,10 +24,9 @@ Kirigami.OverlaySheet {
parent: applicationWindow().overlay parent: applicationWindow().overlay
leftPadding: Kirigami.Units.smallSpacing leftPadding: 0
rightPadding: Kirigami.Units.smallSpacing rightPadding: 0
topPadding: 0 topPadding: 0
bottomPadding: 0
title: i18nc("@title:menu Account detail dialog", "Account detail") title: i18nc("@title:menu Account detail dialog", "Account detail")
@@ -37,8 +36,8 @@ Kirigami.OverlaySheet {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.topMargin: Kirigami.Units.largeSpacing Layout.topMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing Layout.bottomMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar { Kirigami.Avatar {
@@ -48,6 +47,16 @@ Kirigami.OverlaySheet {
name: displayName name: displayName
source: avatarMediaId ? ("image://mxc/" + avatarMediaId) : "" source: avatarMediaId ? ("image://mxc/" + avatarMediaId) : ""
color: user.color color: user.color
MouseArea {
anchors.fill: parent
onClicked: {
if (avatarMediaId) {
fullScreenImage.createObject(parent, {filename: displayName, source: room.urlToMxcUrl(avatarUrl)}).showFullScreen()
}
}
}
} }
ColumnLayout { ColumnLayout {
@@ -70,9 +79,7 @@ Kirigami.OverlaySheet {
} }
} }
Kirigami.Separator { QQC2.MenuSeparator {}
Layout.fillWidth: true
}
Kirigami.BasicListItem { Kirigami.BasicListItem {
visible: user !== room.localUser visible: user !== room.localUser
@@ -97,19 +104,6 @@ Kirigami.OverlaySheet {
} }
} }
} }
Kirigami.BasicListItem {
visible: user !== room.localUser && room.canSendState("invite") && !room.containsUser(user.id)
action: Kirigami.Action {
enabled: !room.isUserBanned(user.id)
text: i18n("Invite this user")
icon.name: "list-add-user"
onTriggered: {
room.inviteToRoom(user.id)
root.close()
}
}
}
Kirigami.BasicListItem { Kirigami.BasicListItem {
visible: user !== room.localUser && room.canSendState("ban") && !room.isUserBanned(user.id) visible: user !== room.localUser && room.canSendState("ban") && !room.isUserBanned(user.id)
@@ -166,14 +160,10 @@ Kirigami.OverlaySheet {
} }
} }
} }
Kirigami.BasicListItem { Component {
action: Kirigami.Action { id: fullScreenImage
text: i18n("Copy link")
icon.name: "username-copy" FullScreenImage {}
onTriggered: {
Clipboard.saveText("https://matrix.to/#/" + user.id)
}
}
} }
} }

View File

@@ -113,7 +113,7 @@ Loader {
QQC2.MenuItem { QQC2.MenuItem {
text: i18n("Room Settings") text: i18n("Room Settings")
icon.name: "configure" icon.name: "configure"
onTriggered: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Room Settings") }) onTriggered: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room})
} }
QQC2.MenuSeparator {} QQC2.MenuSeparator {}
@@ -180,7 +180,7 @@ Loader {
QQC2.ToolButton { QQC2.ToolButton {
icon.name: 'settings-configure' icon.name: 'settings-configure'
onClicked: { onClicked: {
QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Room Settings") }) QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room})
drawer.close() drawer.close()
} }
} }

View File

@@ -21,11 +21,6 @@ Loader {
Component { Component {
id: regularMenu id: regularMenu
QQC2.Menu { QQC2.Menu {
QQC2.MenuItem {
text: i18nc("'Space' is a matrix space", "View Space")
onTriggered: RoomManager.enterRoom(room);
}
QQC2.MenuItem { QQC2.MenuItem {
text: i18nc("@action:inmenu", "Copy Address to Clipboard") text: i18nc("@action:inmenu", "Copy Address to Clipboard")
onTriggered: if (room.canonicalAlias.length === 0) { onTriggered: if (room.canonicalAlias.length === 0) {
@@ -37,7 +32,7 @@ Loader {
QQC2.MenuItem { QQC2.MenuItem {
text: i18nc("'Space' is a matrix space", "Space Settings") text: i18nc("'Space' is a matrix space", "Space Settings")
onTriggered: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Space Settings") }) onTriggered: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room})
} }
QQC2.MenuSeparator {} QQC2.MenuSeparator {}
@@ -94,7 +89,7 @@ Loader {
QQC2.ToolButton { QQC2.ToolButton {
icon.name: 'settings-configure' icon.name: 'settings-configure'
onClicked: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Space Settings") }) onClicked: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room})
} }
} }
Kirigami.BasicListItem { Kirigami.BasicListItem {

View File

@@ -113,13 +113,11 @@ MessageDelegateContextMenu {
id: saveAsDialog id: saveAsDialog
FileDialog { FileDialog {
fileMode: FileDialog.SaveFile fileMode: FileDialog.SaveFile
folder: Config.lastSaveDirectory.length > 0 ? Config.lastSaveDirectory : StandardPaths.writableLocation(StandardPaths.DownloadLocation) folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
onAccepted: { onAccepted: {
if (!currentFile) { if (!currentFile) {
return; return;
} }
Config.lastSaveDirectory = folder
Config.save()
currentRoom.downloadFile(eventId, currentFile) currentRoom.downloadFile(eventId, currentFile)
} }
} }

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