Compare commits
1 Commits
work/nvrwh
...
work/inval
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
739c96711c |
@@ -2,7 +2,7 @@
|
||||
"id": "org.kde.neochat",
|
||||
"branch": "master",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "5.15-22.08",
|
||||
"runtime-version": "5.15-21.08",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"command": "neochat",
|
||||
"tags": [
|
||||
@@ -136,8 +136,8 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.7.0.tar.gz",
|
||||
"sha256": "23ef0217926e67c8d2eb861cf91617da2f7d8d5a9ae6c62321b21448b1669210",
|
||||
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.4.0.tar.gz",
|
||||
"sha256": "0e68b3f0ce7bf521ffbdd731464d2d60d8d7a39a749b551ed26855a1707d86d1",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 236236,
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,4 +9,3 @@ compile_commands.json
|
||||
kate.project.ctags.*
|
||||
*.user
|
||||
.flatpak-builder/
|
||||
.idea/
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
include:
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
|
||||
# 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
|
||||
# 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-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml
|
||||
|
||||
@@ -14,11 +14,6 @@ Dependencies:
|
||||
'frameworks/knotifications': '@stable'
|
||||
'libraries/kquickimageeditor': '@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']
|
||||
'require':
|
||||
'frameworks/qqc2-desktop-style': '@stable'
|
||||
@@ -30,4 +25,4 @@ Dependencies:
|
||||
'frameworks/kdbusaddons': '@stable'
|
||||
|
||||
Options:
|
||||
require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD', 'Windows' ]
|
||||
require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
|
||||
|
||||
@@ -6,19 +6,11 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "23")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "03")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
project(NeoChat)
|
||||
set(PROJECT_VERSION "22.09")
|
||||
|
||||
set(KF5_MIN_VERSION "5.91.0")
|
||||
set(QT_MIN_VERSION "5.15.2")
|
||||
if (ANDROID)
|
||||
set(QT_MIN_VERSION "5.15.8")
|
||||
endif()
|
||||
|
||||
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
@@ -66,7 +58,6 @@ set_package_properties(KF5Kirigami2 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Kirigami application UI framework"
|
||||
)
|
||||
find_package(KF5KirigamiAddons 0.6 REQUIRED)
|
||||
|
||||
find_package(Qt${QT_MAJOR_VERSION}Keychain)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
|
||||
@@ -89,6 +80,8 @@ else()
|
||||
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
|
||||
endif()
|
||||
|
||||
ecm_find_qmlmodule(org.kde.kirigamiaddons.labs.mobileform 0.1)
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
find_package(KF5DBusAddons ${KF5_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
@@ -120,7 +113,10 @@ set_package_properties(KQuickImageEditor PROPERTIES
|
||||
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()
|
||||
|
||||
@@ -130,8 +126,6 @@ set_package_properties(KF5DocTools PROPERTIES DESCRIPTION
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
|
||||
find_package(Sqlite3)
|
||||
|
||||
if(NOT Quotient_VERSION_MINOR GREATER 6)
|
||||
cmake_policy(SET CMP0063 OLD)
|
||||
endif()
|
||||
@@ -151,7 +145,7 @@ add_definitions(-DQT_NO_FOREACH)
|
||||
|
||||
add_subdirectory(src)
|
||||
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)
|
||||
endif()
|
||||
|
||||
|
||||
0
LICENSES/MIT.txt
Normal file → Executable file
0
LICENSES/MIT.txt
Normal file → Executable file
@@ -14,8 +14,7 @@
|
||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||
android:label="NeoChat"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true">
|
||||
android:launchMode="singleTop">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
@@ -23,6 +22,7 @@
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="neochat-app"/>
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||
@@ -38,6 +38,8 @@
|
||||
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
|
||||
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
|
||||
<!-- Messages maps -->
|
||||
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
|
||||
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
|
||||
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
||||
|
||||
<!-- Splash screen -->
|
||||
|
||||
@@ -12,7 +12,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath 'com.android.tools.build:gradle:3.6.4'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,6 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion qtMinSdkVersion
|
||||
targetSdkVersion qtTargetSdkVersion
|
||||
applicationId "org.kde.neochat"
|
||||
namespace "org.kde.neochat"
|
||||
versionCode timestamp
|
||||
versionName projectVersionFull
|
||||
manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
#define protected public // please don't hate me
|
||||
#include "neochatroom.h"
|
||||
#undef protected
|
||||
|
||||
#include <connection.h>
|
||||
#include <quotient_common.h>
|
||||
@@ -13,23 +15,12 @@
|
||||
|
||||
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 {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestRoom *room = nullptr;
|
||||
NeoChatRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
@@ -40,7 +31,7 @@ private Q_SLOTS:
|
||||
void NeoChatRoomTest::initTestCase()
|
||||
{
|
||||
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({
|
||||
"account_data": {
|
||||
@@ -130,7 +121,7 @@ void NeoChatRoomTest::initTestCase()
|
||||
}
|
||||
})EVENT");
|
||||
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
|
||||
room->update(std::move(roomData));
|
||||
room->updateData(std::move(roomData));
|
||||
}
|
||||
|
||||
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 |
@@ -16,7 +16,6 @@
|
||||
<name xml:lang="ca-valencia">NeoChat</name>
|
||||
<name xml:lang="cs">NeoChat</name>
|
||||
<name xml:lang="de">NeoChat</name>
|
||||
<name xml:lang="el">NeoChat</name>
|
||||
<name xml:lang="en-GB">NeoChat</name>
|
||||
<name xml:lang="es">NeoChat</name>
|
||||
<name xml:lang="eu">NeoChat</name>
|
||||
@@ -35,7 +34,6 @@
|
||||
<name xml:lang="pl">NeoChat</name>
|
||||
<name xml:lang="pt">NeoChat</name>
|
||||
<name xml:lang="pt-BR">NeoChat</name>
|
||||
<name xml:lang="ru">NeoChat</name>
|
||||
<name xml:lang="sk">NeoChat</name>
|
||||
<name xml:lang="sl">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="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="el">Ένας πελάτης για το Matrix, το αποκεντρωμένο πρωτόκολλο επικοινωνίας</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="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="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="ru">Клиент для Matrix — децентрализованного коммуникационного протокола</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="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-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="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="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>
|
||||
@@ -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="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="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="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>
|
||||
@@ -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-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="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="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>
|
||||
@@ -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="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="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="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>
|
||||
@@ -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="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="de">NeoChat funktioniert sowohl auf Mobilgeräten als auch auf dem PC und bietet ein einheitliches Benutzererlebnis.</p>
|
||||
<p xml:lang="el">Το NeoChat λειτουργεί και στα κινητά και στους υπολογιστές γραφείου παρέχοντας μια αδιάλειπτη εμπειρία χρήσης.</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="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="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="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="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="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>
|
||||
@@ -185,7 +175,6 @@
|
||||
<developer_name xml:lang="ca-valencia">La comunitat 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="el">Η Κοινότητα του KDE</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="eu">KDE komunitatea</developer_name>
|
||||
@@ -204,7 +193,6 @@
|
||||
<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-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="sl">Skupnost KDE</developer_name>
|
||||
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
||||
@@ -216,7 +204,6 @@
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<custom>
|
||||
<value key="KDE::matrix">#neochat:kde.org</value>
|
||||
<value key="KDE::windows_store">https://www.microsoft.com/store/apps/9PNXWVNRC29H</value>
|
||||
</custom>
|
||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||
<screenshots>
|
||||
@@ -231,23 +218,6 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="23.01" date="2023-01-30">
|
||||
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
|
||||
<description>
|
||||
<p>New features and bugfixes:</p>
|
||||
<ul>
|
||||
<li>Notifications will now be shown for all accounts, not just the active one</li>
|
||||
<li>There is a new "compact" mode for the room list</li>
|
||||
<li>You can now search in the room history</li>
|
||||
<li>Emojis and Reactions have been significantly improved</li>
|
||||
<li>Fixed several crashes around user invitations</li>
|
||||
<li>Room permission settings can now be configured</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="22.11" date="2022-11-30">
|
||||
<url>https://plasma-mobile.org/2022/11/30/plasma-mobile-gear-22-11/</url>
|
||||
</release>
|
||||
<release version="22.09" date="2022-09-27">
|
||||
<url>https://www.plasma-mobile.org/2022/09/27/plasma-mobile-gear-22-09/</url>
|
||||
</release>
|
||||
|
||||
@@ -9,7 +9,6 @@ Name[ca]=NeoChat
|
||||
Name[ca@valencia]=NeoChat
|
||||
Name[cs]=NeoChat
|
||||
Name[de]=NeoChat
|
||||
Name[el]=NeoChat
|
||||
Name[en_GB]=NeoChat
|
||||
Name[es]=NeoChat
|
||||
Name[eu]=NeoChat
|
||||
@@ -30,7 +29,6 @@ Name[pl]=NeoChat
|
||||
Name[pt]=NeoChat
|
||||
Name[pt_BR]=NeoChat
|
||||
Name[ro]=NeoChat
|
||||
Name[ru]=NeoChat
|
||||
Name[sk]=NeoChat
|
||||
Name[sl]=NeoChat
|
||||
Name[sv]=NeoChat
|
||||
@@ -46,7 +44,6 @@ GenericName[ca]=Client de Matrix
|
||||
GenericName[ca@valencia]=Client de Matrix
|
||||
GenericName[cs]=Klient protokolu Matrix
|
||||
GenericName[de]=Matrix-Programm
|
||||
GenericName[el]=Εφαρμογή του Matrix
|
||||
GenericName[en_GB]=Matrix Client
|
||||
GenericName[es]=Cliente para Matrix
|
||||
GenericName[eu]=Matrix bezeroa
|
||||
@@ -67,7 +64,6 @@ GenericName[pl]=Program Matriksa
|
||||
GenericName[pt]=Cliente de Matrix
|
||||
GenericName[pt_BR]=Cliente Matrix
|
||||
GenericName[ro]=Client Matrix
|
||||
GenericName[ru]=Клиент Matrix
|
||||
GenericName[sk]=Matrix Client
|
||||
GenericName[sl]=Odjemalec Matrix
|
||||
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@valencia]=Client per al protocol Matrix
|
||||
Comment[de]=Programm für das Matrix-Protokoll
|
||||
Comment[el]=Πελάτης για το πρωτόκολλο Matrix
|
||||
Comment[en_GB]=Client for the Matrix protocol
|
||||
Comment[es]=Cliente para el protocolo Matrix
|
||||
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_BR]=Cliente para o protocolo Matrix
|
||||
Comment[ro]=Client pentru protocolul Matrix
|
||||
Comment[ru]=Клиент для протокола Matrix
|
||||
Comment[sk]=Klient protokolu Matrix
|
||||
Comment[sl]=Odjemalec za protokol Matrix
|
||||
Comment[sv]=Klient för protokollet Matrix
|
||||
|
||||
2148
po/ar/neochat.po
2148
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
2323
po/az/neochat.po
2323
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
2130
po/ca/neochat.po
2130
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2437
po/cs/neochat.po
2437
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
2120
po/da/neochat.po
2120
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
2848
po/de/neochat.po
2848
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
3779
po/el/neochat.po
3779
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
2777
po/en_GB/neochat.po
2777
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
2177
po/es/neochat.po
2177
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
2165
po/eu/neochat.po
2165
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
2317
po/fi/neochat.po
2317
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
2355
po/fr/neochat.po
2355
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
2308
po/hu/neochat.po
2308
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
2582
po/ia/neochat.po
2582
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
2334
po/id/neochat.po
2334
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
2197
po/ie/neochat.po
2197
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -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&component=General"
|
||||
>https://bugs.kde.org/enter_bug.cgi? product=NeoChat&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 © 2020-2022 Tobias Fella </para>
|
||||
<para
|
||||
>Copyright © 2020-2022 Carl Schwan </para>
|
||||
<para
|
||||
>Licenza: GNU General Public Version 3 o successiva <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
||||
>></para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
2548
po/it/neochat.po
2548
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
2053
po/ja/neochat.po
2053
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
2322
po/ka/neochat.po
2322
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
2300
po/ko/neochat.po
2300
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
2167
po/nl/neochat.po
2167
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
2028
po/nn/neochat.po
2028
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
2270
po/pa/neochat.po
2270
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
2659
po/pl/neochat.po
2659
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
2265
po/pt/neochat.po
2265
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
2324
po/pt_BR/neochat.po
2324
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
3349
po/ru/neochat.po
3349
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
2316
po/sk/neochat.po
2316
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
2237
po/sl/neochat.po
2237
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
2321
po/sv/neochat.po
2321
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
2278
po/ta/neochat.po
2278
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
2132
po/tok/neochat.po
2132
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
2189
po/tr/neochat.po
2189
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
2162
po/uk/neochat.po
2162
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
2484
po/zh_CN/neochat.po
2484
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
2067
po/zh_TW/neochat.po
2067
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -6,47 +6,44 @@
|
||||
add_library(neochat STATIC
|
||||
controller.cpp
|
||||
actionshandler.cpp
|
||||
models/emojimodel.cpp
|
||||
emojitones.cpp
|
||||
models/customemojimodel.cpp
|
||||
emojimodel.cpp
|
||||
customemojimodel.cpp
|
||||
clipboard.cpp
|
||||
matriximageprovider.cpp
|
||||
models/messageeventmodel.cpp
|
||||
models/messagefiltermodel.cpp
|
||||
models/roomlistmodel.cpp
|
||||
models/sortfilterspacelistmodel.cpp
|
||||
messageeventmodel.cpp
|
||||
messagefiltermodel.cpp
|
||||
roomlistmodel.cpp
|
||||
sortfilterspacelistmodel.cpp
|
||||
spacehierarchycache.cpp
|
||||
roommanager.cpp
|
||||
neochatroom.cpp
|
||||
neochatuser.cpp
|
||||
models/userlistmodel.cpp
|
||||
models/userfiltermodel.cpp
|
||||
models/publicroomlistmodel.cpp
|
||||
models/userdirectorylistmodel.cpp
|
||||
models/keywordnotificationrulemodel.cpp
|
||||
userlistmodel.cpp
|
||||
publicroomlistmodel.cpp
|
||||
userdirectorylistmodel.cpp
|
||||
keywordnotificationrulemodel.cpp
|
||||
utils.cpp
|
||||
notificationsmanager.cpp
|
||||
models/sortfilterroomlistmodel.cpp
|
||||
sortfilterroomlistmodel.cpp
|
||||
chatdocumenthandler.cpp
|
||||
models/devicesmodel.cpp
|
||||
devicesmodel.cpp
|
||||
filetypesingleton.cpp
|
||||
login.cpp
|
||||
stickerevent.cpp
|
||||
models/webshortcutmodel.cpp
|
||||
webshortcutmodel.cpp
|
||||
blurhash.cpp
|
||||
blurhashimageprovider.cpp
|
||||
joinrulesevent.cpp
|
||||
models/collapsestateproxymodel.cpp
|
||||
collapsestateproxymodel.cpp
|
||||
urlhelper.cpp
|
||||
windowcontroller.cpp
|
||||
linkpreviewer.cpp
|
||||
models/completionmodel.cpp
|
||||
models/completionproxymodel.cpp
|
||||
models/actionsmodel.cpp
|
||||
models/serverlistmodel.cpp
|
||||
models/statemodel.cpp
|
||||
completionmodel.cpp
|
||||
completionproxymodel.cpp
|
||||
actionsmodel.cpp
|
||||
serverlistmodel.cpp
|
||||
statemodel.cpp
|
||||
filetransferpseudojob.cpp
|
||||
models/searchmodel.cpp
|
||||
)
|
||||
|
||||
add_executable(neochat-app
|
||||
@@ -70,7 +67,7 @@ endif()
|
||||
|
||||
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)
|
||||
target_sources(neochat PRIVATE colorschemer.cpp)
|
||||
@@ -93,7 +90,12 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::SonnetCore KF5::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 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)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
@@ -103,9 +105,6 @@ endif()
|
||||
if(ANDROID)
|
||||
target_sources(neochat PRIVATE notifyrc.qrc)
|
||||
target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL)
|
||||
if(SQLite3_FOUND)
|
||||
target_link_libraries(neochat-app PRIVATE SQLite::SQLite3)
|
||||
endif()
|
||||
target_sources(neochat-app PRIVATE notifyrc.qrc)
|
||||
target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL)
|
||||
kirigami_package_breeze_icons(ICONS
|
||||
@@ -162,18 +161,10 @@ if(ANDROID)
|
||||
"zoom-out"
|
||||
"image-rotate-left-symbolic"
|
||||
"image-rotate-right-symbolic"
|
||||
"channel-secure-symbolic"
|
||||
"channel-insecure-symbolic"
|
||||
"download"
|
||||
"smiley"
|
||||
"tools-check-spelling"
|
||||
"username-copy"
|
||||
"system-switch-user"
|
||||
"bookmark-new"
|
||||
"bookmark-remove"
|
||||
"favorite"
|
||||
"window-new"
|
||||
"globe"
|
||||
"visibility"
|
||||
)
|
||||
else()
|
||||
target_link_libraries(neochat PUBLIC Qt::Widgets KF5::KIOWidgets)
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
#include <KLocalizedString>
|
||||
#include <QStringBuilder>
|
||||
|
||||
#include "actionsmodel.h"
|
||||
#include "controller.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "customemojimodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include "roommanager.h"
|
||||
#include "neochatuser.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -59,9 +59,9 @@ void ActionsHandler::setRoom(NeoChatRoom *room)
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
void ActionsHandler::handleNewMessage()
|
||||
void ActionsHandler::handleMessage()
|
||||
{
|
||||
checkEffects(m_room->chatBoxText());
|
||||
checkEffects();
|
||||
if (!m_room->chatBoxAttachmentPath().isEmpty()) {
|
||||
QUrl url(m_room->chatBoxAttachmentPath());
|
||||
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||
@@ -70,39 +70,13 @@ void ActionsHandler::handleNewMessage()
|
||||
m_room->setChatBoxText({});
|
||||
return;
|
||||
}
|
||||
|
||||
QString handledText = m_room->chatBoxText();
|
||||
handledText = handleMentions(handledText);
|
||||
handleMessage(m_room->chatBoxText(), handledText);
|
||||
}
|
||||
|
||||
void ActionsHandler::handleEdit()
|
||||
{
|
||||
checkEffects(m_room->editText());
|
||||
|
||||
QString handledText = m_room->editText();
|
||||
handledText = handleMentions(handledText, true);
|
||||
handleMessage(m_room->editText(), handledText, true);
|
||||
}
|
||||
|
||||
QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
|
||||
{
|
||||
if (!m_room) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QVector<Mention> *mentions;
|
||||
if (isEdit) {
|
||||
mentions = m_room->editMentions();
|
||||
} else {
|
||||
mentions = m_room->mentions();
|
||||
}
|
||||
|
||||
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
|
||||
std::sort(m_room->mentions()->begin(), m_room->mentions()->end(), [](const auto &a, const auto &b) -> bool {
|
||||
return a.cursor.anchor() > b.cursor.anchor();
|
||||
});
|
||||
|
||||
for (const auto &mention : *mentions) {
|
||||
for (const auto &mention : *m_room->mentions()) {
|
||||
if (mention.text.isEmpty() || mention.id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
@@ -110,16 +84,11 @@ QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
|
||||
mention.cursor.position() - mention.cursor.anchor(),
|
||||
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text, mention.id));
|
||||
}
|
||||
mentions->clear();
|
||||
m_room->mentions()->clear();
|
||||
|
||||
return handledText;
|
||||
}
|
||||
|
||||
void ActionsHandler::handleMessage(const QString &text, QString handledText, const bool &isEdit)
|
||||
{
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed("^s/([^/]*)/([^/]*)(/g)?$");
|
||||
auto match = sed.match(text);
|
||||
auto match = sed.match(m_room->chatBoxText());
|
||||
if (match.hasMatch()) {
|
||||
const QString regex = match.captured(1);
|
||||
const QString replacement = match.captured(2).toHtmlEscaped();
|
||||
@@ -170,21 +139,17 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, con
|
||||
|
||||
handledText = CustomEmojiModel::instance().preprocessText(handledText);
|
||||
handledText = markdownToHTML(handledText);
|
||||
if (handledText.count("<p>") == 1 && handledText.count("</p>") == 1) {
|
||||
handledText.remove("<p>");
|
||||
handledText.remove("</p>");
|
||||
}
|
||||
|
||||
if (handledText.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_room->postMessage(text, handledText, messageType, m_room->chatBoxReplyId(), isEdit ? m_room->chatBoxEditId() : "");
|
||||
m_room->postMessage(m_room->chatBoxText(), handledText, messageType, m_room->chatBoxReplyId(), m_room->chatBoxEditId());
|
||||
}
|
||||
|
||||
void ActionsHandler::checkEffects(const QString &text)
|
||||
void ActionsHandler::checkEffects()
|
||||
{
|
||||
std::optional<QString> effect = std::nullopt;
|
||||
const auto &text = m_room->chatBoxText();
|
||||
if (text.contains("\u2744")) {
|
||||
effect = QLatin1String("snowflake");
|
||||
} else if (text.contains("\u1F386")) {
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
#include <events/roommessageevent.h>
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
class CustomEmojiModel;
|
||||
class NeoChatRoom;
|
||||
|
||||
@@ -33,22 +32,14 @@ Q_SIGNALS:
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
/**
|
||||
* @brief Pre-process text and send message.
|
||||
*/
|
||||
void handleNewMessage();
|
||||
|
||||
/**
|
||||
* @brief Pre-process text and send edit.
|
||||
*/
|
||||
void handleEdit();
|
||||
/// \brief Post a message.
|
||||
///
|
||||
/// This also interprets commands if any.
|
||||
void handleMessage();
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
void checkEffects(const QString &text);
|
||||
|
||||
QString handleMentions(QString handledText, const bool &isEdit = false);
|
||||
void handleMessage(const QString &text, QString handledText, const bool &isEdit = false);
|
||||
void checkEffects();
|
||||
};
|
||||
|
||||
QString markdownToHTML(const QString &markdown);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "controller.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include "roommanager.h"
|
||||
#include <events/roommemberevent.h>
|
||||
#include <events/roompowerlevelsevent.h>
|
||||
|
||||
@@ -158,12 +157,11 @@ QVector<ActionsModel::Action> actions{
|
||||
return QString();
|
||||
}
|
||||
#ifdef QUOTIENT_07
|
||||
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
|
||||
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
|
||||
if (room->currentState().get<RoomMemberEvent>(text)->membership() == Membership::Invite) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
||||
return QString();
|
||||
}
|
||||
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Ban) {
|
||||
if (room->currentState().get<RoomMemberEvent>(text)->membership() == Membership::Ban) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
||||
return QString();
|
||||
}
|
||||
@@ -195,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));
|
||||
return QString();
|
||||
}
|
||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||
if (targetRoom) {
|
||||
RoomManager::instance().enterRoom(dynamic_cast<NeoChatRoom *>(targetRoom));
|
||||
if (Controller::instance().activeConnection()->room(text) || Controller::instance().activeConnection()->roomByAlias(text)) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <QQmlFile>
|
||||
#include <QQmlFileSelector>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QStringBuilder>
|
||||
#include <QSyntaxHighlighter>
|
||||
#include <QTextBlock>
|
||||
@@ -14,9 +15,10 @@
|
||||
#include <Sonnet/BackgroundChecker>
|
||||
#include <Sonnet/Settings>
|
||||
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/roomlistmodel.h"
|
||||
#include "actionsmodel.h"
|
||||
#include "completionmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roomlistmodel.h"
|
||||
|
||||
class SyntaxHighlighter : public QSyntaxHighlighter
|
||||
{
|
||||
@@ -105,19 +107,14 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
|
||||
{
|
||||
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
|
||||
m_completionModel->setRoom(m_room);
|
||||
static QPointer<NeoChatRoom> previousRoom = nullptr;
|
||||
static NeoChatRoom *previousRoom = nullptr;
|
||||
if (previousRoom) {
|
||||
disconnect(previousRoom, &NeoChatRoom::chatBoxTextChanged, this, nullptr);
|
||||
disconnect(previousRoom, &NeoChatRoom::editTextChanged, this, nullptr);
|
||||
}
|
||||
previousRoom = m_room;
|
||||
connect(m_room, &NeoChatRoom::chatBoxTextChanged, this, [this]() {
|
||||
int start = completionStartIndex();
|
||||
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::editTextChanged, this, [this]() {
|
||||
int start = completionStartIndex();
|
||||
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
|
||||
m_completionModel->setText(m_room->chatBoxText().mid(start, cursorPosition() - start), m_room->chatBoxText().mid(start));
|
||||
});
|
||||
});
|
||||
connect(this, &ChatDocumentHandler::documentChanged, this, [this]() {
|
||||
@@ -128,7 +125,7 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
|
||||
return;
|
||||
}
|
||||
int start = completionStartIndex();
|
||||
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
|
||||
m_completionModel->setText(m_room->chatBoxText().mid(start, cursorPosition() - start), m_room->chatBoxText().mid(start));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -138,12 +135,8 @@ int ChatDocumentHandler::completionStartIndex() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
||||
const long long cursor = cursorPosition();
|
||||
#else
|
||||
const auto cursor = cursorPosition();
|
||||
#endif
|
||||
const auto &text = getText();
|
||||
const auto &cursor = cursorPosition();
|
||||
const auto &text = m_room->chatBoxText();
|
||||
auto start = std::min(cursor, text.size()) - 1;
|
||||
while (start > -1) {
|
||||
if (text.at(start) == QLatin1Char(' ')) {
|
||||
@@ -155,20 +148,6 @@ int ChatDocumentHandler::completionStartIndex() const
|
||||
return start;
|
||||
}
|
||||
|
||||
bool ChatDocumentHandler::isEdit() const
|
||||
{
|
||||
return m_isEdit;
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::setIsEdit(bool edit)
|
||||
{
|
||||
if (edit == m_isEdit) {
|
||||
return;
|
||||
}
|
||||
m_isEdit = edit;
|
||||
Q_EMIT isEditChanged();
|
||||
}
|
||||
|
||||
QQuickTextDocument *ChatDocumentHandler::document() const
|
||||
{
|
||||
return m_document;
|
||||
@@ -220,10 +199,10 @@ void ChatDocumentHandler::setRoom(NeoChatRoom *room)
|
||||
|
||||
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 id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Subtitle).toString();
|
||||
auto text = getText();
|
||||
auto text = m_room->chatBoxText();
|
||||
auto at = text.lastIndexOf(QLatin1Char('@'), cursorPosition() - 1);
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
@@ -232,19 +211,19 @@ void ChatDocumentHandler::complete(int index)
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursor.position() + name.size(), QTextCursor::KeepAnchor);
|
||||
cursor.setKeepPositionOnInsert(true);
|
||||
pushMention({cursor, name, 0, 0, id});
|
||||
m_room->mentions()->push_back({cursor, name, 0, 0, id});
|
||||
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 text = getText();
|
||||
auto text = m_room->chatBoxText();
|
||||
auto at = text.lastIndexOf(QLatin1Char('/'));
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
|
||||
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 text = getText();
|
||||
auto text = m_room->chatBoxText();
|
||||
auto at = text.lastIndexOf(QLatin1Char('#'), cursorPosition() - 1);
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
@@ -253,11 +232,11 @@ void ChatDocumentHandler::complete(int index)
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursor.position() + alias.size(), QTextCursor::KeepAnchor);
|
||||
cursor.setKeepPositionOnInsert(true);
|
||||
pushMention({cursor, alias, 0, 0, alias});
|
||||
m_room->mentions()->push_back({cursor, alias, 0, 0, alias});
|
||||
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 text = getText();
|
||||
auto text = m_room->chatBoxText();
|
||||
auto at = text.lastIndexOf(QLatin1Char(':'));
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
@@ -300,27 +279,3 @@ void ChatDocumentHandler::setSelectionEnd(int position)
|
||||
m_selectionEnd = position;
|
||||
Q_EMIT selectionEndChanged();
|
||||
}
|
||||
|
||||
QString ChatDocumentHandler::getText() const
|
||||
{
|
||||
if (!m_room) {
|
||||
return QString();
|
||||
}
|
||||
if (m_isEdit) {
|
||||
return m_room->editText();
|
||||
} else {
|
||||
return m_room->chatBoxText();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::pushMention(const Mention mention) const
|
||||
{
|
||||
if (!m_room) {
|
||||
return;
|
||||
}
|
||||
if (m_isEdit) {
|
||||
m_room->editMentions()->push_back(mention);
|
||||
} else {
|
||||
m_room->mentions()->push_back(mention);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,28 +4,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQuickTextDocument>
|
||||
|
||||
#include <QTextCursor>
|
||||
|
||||
#include "models/completionmodel.h"
|
||||
#include "models/userlistmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "userlistmodel.h"
|
||||
|
||||
class QTextDocument;
|
||||
class QQuickTextDocument;
|
||||
class NeoChatRoom;
|
||||
class SyntaxHighlighter;
|
||||
class CompletionModel;
|
||||
|
||||
class ChatDocumentHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief Is the instance being used to handle an edit message.
|
||||
*
|
||||
* This is needed to ensure that the text and mentions are saved and retrieved
|
||||
* from the correct parameters in the assigned room.
|
||||
*/
|
||||
Q_PROPERTY(bool isEdit READ isEdit WRITE setIsEdit NOTIFY isEditChanged)
|
||||
Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged)
|
||||
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
|
||||
Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged)
|
||||
@@ -33,13 +25,19 @@ class ChatDocumentHandler : public QObject
|
||||
|
||||
Q_PROPERTY(CompletionModel *completionModel READ completionModel NOTIFY completionModelChanged)
|
||||
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
Q_PROPERTY(NeoChatRoom *room READ room NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
||||
enum AutoCompletionType {
|
||||
User,
|
||||
Room,
|
||||
Emoji,
|
||||
Command,
|
||||
None,
|
||||
};
|
||||
Q_ENUM(AutoCompletionType)
|
||||
|
||||
[[nodiscard]] bool isEdit() const;
|
||||
void setIsEdit(bool edit);
|
||||
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] QQuickTextDocument *document() const;
|
||||
void setDocument(QQuickTextDocument *document);
|
||||
@@ -61,7 +59,6 @@ public:
|
||||
void updateCompletions();
|
||||
CompletionModel *completionModel() const;
|
||||
Q_SIGNALS:
|
||||
void isEditChanged();
|
||||
void documentChanged();
|
||||
void cursorPositionChanged();
|
||||
void roomChanged();
|
||||
@@ -72,8 +69,6 @@ Q_SIGNALS:
|
||||
private:
|
||||
int completionStartIndex() const;
|
||||
|
||||
bool m_isEdit;
|
||||
|
||||
QQuickTextDocument *m_document;
|
||||
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
@@ -83,12 +78,11 @@ private:
|
||||
int m_selectionStart;
|
||||
int m_selectionEnd;
|
||||
|
||||
QString getText() const;
|
||||
void pushMention(const Mention mention) const;
|
||||
|
||||
SyntaxHighlighter *m_highlighter = nullptr;
|
||||
|
||||
CompletionModel::AutoCompletionType m_completionType = CompletionModel::None;
|
||||
AutoCompletionType m_completionType = None;
|
||||
|
||||
CompletionModel *m_completionModel = nullptr;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ChatDocumentHandler::AutoCompletionType);
|
||||
|
||||
@@ -33,14 +33,13 @@ QImage Clipboard::image() const
|
||||
|
||||
QString Clipboard::saveImage(QString localPath) const
|
||||
{
|
||||
QString imageDir(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
|
||||
|
||||
if (!QDir().exists(imageDir)) {
|
||||
QDir().mkdir(imageDir);
|
||||
if (!QDir().exists(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)))) {
|
||||
QDir().mkdir(QStringLiteral("%1/screenshots").arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
|
||||
}
|
||||
|
||||
if (localPath.isEmpty()) {
|
||||
localPath = QStringLiteral("file://%1/%2.png").arg(imageDir, QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd-hh-mm-ss")));
|
||||
localPath = QStringLiteral("file://%1/screenshots/%2.png")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
|
||||
QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd-hh-mm-ss")));
|
||||
}
|
||||
QUrl url(localPath);
|
||||
if (!url.isLocalFile()) {
|
||||
@@ -52,11 +51,14 @@ QString Clipboard::saveImage(QString localPath) const
|
||||
return {};
|
||||
}
|
||||
|
||||
if (image.save(url.toLocalFile())) {
|
||||
return localPath;
|
||||
} else {
|
||||
return {};
|
||||
QDir dir;
|
||||
if (!dir.exists(localPath)) {
|
||||
dir.mkpath(localPath);
|
||||
}
|
||||
|
||||
image.save(url.toLocalFile());
|
||||
|
||||
return localPath;
|
||||
}
|
||||
|
||||
void Clipboard::saveText(QString message)
|
||||
|
||||
86
src/collapsestateproxymodel.cpp
Normal file
86
src/collapsestateproxymodel.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "collapsestateproxymodel.h"
|
||||
#include "messageeventmodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool() // If it's a new day, show it
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventResolvedTypeRole)
|
||||
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0),
|
||||
MessageEventModel::EventResolvedTypeRole) // Also show it if it's of a different type than the one before TODO improve in
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::AuthorIdRole)
|
||||
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::AuthorIdRole); // Also show it if it's a different author
|
||||
}
|
||||
|
||||
QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == AggregateDisplayRole) {
|
||||
return aggregateEventToString(mapToSource(index).row());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
||||
{
|
||||
auto roles = sourceModel()->roleNames();
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
return roles;
|
||||
}
|
||||
|
||||
QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
{
|
||||
QStringList parts;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
parts += sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString();
|
||||
if (sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| (i > 0
|
||||
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventResolvedTypeRole)
|
||||
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventResolvedTypeRole)) // or of a different type
|
||||
|| (i > 0
|
||||
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorIdRole)
|
||||
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::AuthorIdRole)) // or by a different author
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!parts.isEmpty()) {
|
||||
QStringList chunks;
|
||||
while (!parts.isEmpty()) {
|
||||
chunks += QString();
|
||||
int count = 1;
|
||||
auto part = parts.takeFirst();
|
||||
chunks.last() += part;
|
||||
while (!parts.isEmpty() && parts.first() == part) {
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
if (count > 1) {
|
||||
chunks.last() += i18ncp("[user did something] n times", " %1 time", " %1 times", count);
|
||||
}
|
||||
}
|
||||
QString text = chunks.takeFirst();
|
||||
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
text += i18nc("[action 1], [action 2 and action 3]", ", ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
text += i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
return text;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -12,29 +12,10 @@ class CollapseStateProxyModel : public QSortFilterProxyModel
|
||||
public:
|
||||
enum Roles {
|
||||
AggregateDisplayRole = MessageEventModel::LastRole + 1,
|
||||
StateEventsRole,
|
||||
AuthorListRole,
|
||||
};
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief QString aggregating the text of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QString aggregateEventToString(int row) const;
|
||||
/**
|
||||
* @brief Return a list of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
||||
/**
|
||||
* @brief List of unique authors for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QVariantList authorList(int row) const;
|
||||
};
|
||||
@@ -10,13 +10,14 @@
|
||||
#include "customemojimodel.h"
|
||||
#include "emojimodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roomlistmodel.h"
|
||||
#include "userlistmodel.h"
|
||||
|
||||
CompletionModel::CompletionModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_filterModel(new CompletionProxyModel())
|
||||
, m_userListModel(new UserListModel(this))
|
||||
, m_emojiModel(new QConcatenateTablesProxyModel(this))
|
||||
, m_emojiModel(new KConcatenateRowsProxyModel(this))
|
||||
{
|
||||
connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion);
|
||||
connect(this, &CompletionModel::roomChanged, this, [this]() {
|
||||
@@ -41,7 +42,7 @@ void CompletionModel::setText(const QString &text, const QString &fullText)
|
||||
int CompletionModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
if (m_autoCompletionType == None) {
|
||||
if (m_autoCompletionType == ChatDocumentHandler::None) {
|
||||
return 0;
|
||||
}
|
||||
return m_filterModel->rowCount();
|
||||
@@ -53,7 +54,7 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
|
||||
return {};
|
||||
}
|
||||
auto filterIndex = m_filterModel->index(index.row(), 0);
|
||||
if (m_autoCompletionType == User) {
|
||||
if (m_autoCompletionType == ChatDocumentHandler::User) {
|
||||
if (role == Text) {
|
||||
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) {
|
||||
return m_filterModel->data(filterIndex, ActionsModel::Prefix).toString() + QStringLiteral(" ")
|
||||
+ 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);
|
||||
}
|
||||
}
|
||||
if (m_autoCompletionType == Room) {
|
||||
if (m_autoCompletionType == ChatDocumentHandler::Room) {
|
||||
if (role == Text) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (m_autoCompletionType == Emoji) {
|
||||
if (m_autoCompletionType == ChatDocumentHandler::Emoji) {
|
||||
if (role == Text) {
|
||||
return m_filterModel->data(filterIndex, CustomEmojiModel::DisplayRole);
|
||||
}
|
||||
@@ -101,9 +102,6 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
|
||||
if (role == ReplacedText) {
|
||||
return m_filterModel->data(filterIndex, CustomEmojiModel::ReplacedTextRole);
|
||||
}
|
||||
if (role == Subtitle) {
|
||||
return m_filterModel->data(filterIndex, EmojiModel::DescriptionRole);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -127,7 +125,7 @@ void CompletionModel::updateCompletion()
|
||||
m_filterModel->setSecondaryFilterRole(UserListModel::NameRole);
|
||||
m_filterModel->setFullText(m_fullText);
|
||||
m_filterModel->setFilterText(m_text);
|
||||
m_autoCompletionType = User;
|
||||
m_autoCompletionType = ChatDocumentHandler::User;
|
||||
m_filterModel->invalidate();
|
||||
} else if (text().startsWith(QLatin1Char('/'))) {
|
||||
m_filterModel->setSourceModel(&ActionsModel::instance());
|
||||
@@ -135,10 +133,10 @@ void CompletionModel::updateCompletion()
|
||||
m_filterModel->setSecondaryFilterRole(-1);
|
||||
m_filterModel->setFullText(m_fullText);
|
||||
m_filterModel->setFilterText(m_text.mid(1));
|
||||
m_autoCompletionType = Command;
|
||||
m_autoCompletionType = ChatDocumentHandler::Command;
|
||||
m_filterModel->invalidate();
|
||||
} else if (text().startsWith(QLatin1Char('#'))) {
|
||||
m_autoCompletionType = Room;
|
||||
m_autoCompletionType = ChatDocumentHandler::Room;
|
||||
m_filterModel->setSourceModel(m_roomListModel);
|
||||
m_filterModel->setFilterRole(RoomListModel::CanonicalAliasRole);
|
||||
m_filterModel->setSecondaryFilterRole(RoomListModel::DisplayNameRole);
|
||||
@@ -148,15 +146,15 @@ void CompletionModel::updateCompletion()
|
||||
} else if (text().startsWith(QLatin1Char(':'))
|
||||
&& (m_fullText.indexOf(QLatin1Char(':'), 1) == -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_autoCompletionType = Emoji;
|
||||
m_filterModel->setFilterRole(CustomEmojiModel::Name);
|
||||
m_filterModel->setSecondaryFilterRole(EmojiModel::DescriptionRole);
|
||||
m_filterModel->setSecondaryFilterRole(-1);
|
||||
m_filterModel->setFullText(m_fullText);
|
||||
m_filterModel->setFilterText(m_text);
|
||||
m_filterModel->invalidate();
|
||||
} else {
|
||||
m_autoCompletionType = None;
|
||||
m_autoCompletionType = ChatDocumentHandler::None;
|
||||
}
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
@@ -173,12 +171,12 @@ void CompletionModel::setRoom(NeoChatRoom *room)
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
CompletionModel::AutoCompletionType CompletionModel::autoCompletionType() const
|
||||
ChatDocumentHandler::AutoCompletionType CompletionModel::autoCompletionType() const
|
||||
{
|
||||
return m_autoCompletionType;
|
||||
}
|
||||
|
||||
void CompletionModel::setAutoCompletionType(AutoCompletionType autoCompletionType)
|
||||
void CompletionModel::setAutoCompletionType(ChatDocumentHandler::AutoCompletionType autoCompletionType)
|
||||
{
|
||||
m_autoCompletionType = autoCompletionType;
|
||||
Q_EMIT autoCompletionTypeChanged();
|
||||
@@ -3,10 +3,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QConcatenateTablesProxyModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "roomlistmodel.h"
|
||||
#include <KConcatenateRowsProxyModel>
|
||||
|
||||
#include "chatdocumenthandler.h"
|
||||
|
||||
class CompletionProxyModel;
|
||||
class UserListModel;
|
||||
@@ -18,19 +19,10 @@ class CompletionModel : public QAbstractListModel
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString text READ text NOTIFY textChanged)
|
||||
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);
|
||||
|
||||
public:
|
||||
enum AutoCompletionType {
|
||||
User,
|
||||
Room,
|
||||
Emoji,
|
||||
Command,
|
||||
None,
|
||||
};
|
||||
Q_ENUM(AutoCompletionType)
|
||||
|
||||
enum Roles {
|
||||
Text = Qt::DisplayRole,
|
||||
Subtitle,
|
||||
@@ -55,7 +47,7 @@ public:
|
||||
RoomListModel *roomListModel() const;
|
||||
void setRoomListModel(RoomListModel *roomListModel);
|
||||
|
||||
AutoCompletionType autoCompletionType() const;
|
||||
ChatDocumentHandler::AutoCompletionType autoCompletionType() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void textChanged();
|
||||
@@ -68,12 +60,11 @@ private:
|
||||
QString m_fullText;
|
||||
CompletionProxyModel *m_filterModel;
|
||||
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;
|
||||
RoomListModel *m_roomListModel;
|
||||
QConcatenateTablesProxyModel *m_emojiModel;
|
||||
KConcatenateRowsProxyModel *m_emojiModel;
|
||||
};
|
||||
Q_DECLARE_METATYPE(CompletionModel::AutoCompletionType);
|
||||
@@ -18,22 +18,7 @@ bool CompletionProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &so
|
||||
&& sourceModel()
|
||||
->data(sourceModel()->index(sourceRow, 0), secondaryFilterRole())
|
||||
.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));
|
||||
#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
|
||||
@@ -13,7 +13,6 @@ class CompletionProxyModel : public QSortFilterProxyModel
|
||||
|
||||
public:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
int secondaryFilterRole() const;
|
||||
void setSecondaryFilterRole(int role);
|
||||
@@ -4,11 +4,7 @@
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <qt5keychain/keychain.h>
|
||||
#else
|
||||
#include <qt6keychain/keychain.h>
|
||||
#endif
|
||||
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
@@ -23,6 +19,7 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QImageReader>
|
||||
#include <QNetworkProxy>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QQuickWindow>
|
||||
#include <QStandardPaths>
|
||||
@@ -132,7 +129,16 @@ Controller::Controller(QObject *parent)
|
||||
if (AccountRegistry::instance().size() > oldAccountCount) {
|
||||
auto connection = AccountRegistry::instance().accounts()[AccountRegistry::instance().size() - 1];
|
||||
connect(connection, &Connection::syncDone, this, [=]() {
|
||||
handleNotifications(connection);
|
||||
bool changes = false;
|
||||
for (const auto &room : connection->allRooms()) {
|
||||
if (m_notificationCounts[room] != room->unreadStats().notableCount) {
|
||||
m_notificationCounts[room] = room->unreadStats().notableCount;
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
if (changes) {
|
||||
handleNotifications();
|
||||
}
|
||||
});
|
||||
}
|
||||
oldAccountCount = AccountRegistry::instance().size();
|
||||
@@ -141,16 +147,19 @@ Controller::Controller(QObject *parent)
|
||||
}
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
||||
void Controller::handleNotifications()
|
||||
{
|
||||
static QStringList initial;
|
||||
static bool initial = true;
|
||||
static QStringList oldNotifications;
|
||||
auto job = connection->callApi<GetNotificationsJob>();
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
auto job = m_connection->callApi<GetNotificationsJob>();
|
||||
|
||||
connect(job, &BaseJob::success, this, [job, connection]() {
|
||||
connect(job, &BaseJob::success, this, [this, job]() {
|
||||
const auto notifications = job->jsonData()["notifications"].toArray();
|
||||
if (!initial.contains(connection->user()->id())) {
|
||||
initial.append(connection->user()->id());
|
||||
if (initial) {
|
||||
initial = false;
|
||||
for (const auto &n : notifications) {
|
||||
oldNotifications += n.toObject()["event"].toObject()["event_id"].toString();
|
||||
}
|
||||
@@ -166,7 +175,7 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
||||
continue;
|
||||
}
|
||||
oldNotifications += notification["event"].toObject()["event_id"].toString();
|
||||
auto room = connection->room(notification["room_id"].toString());
|
||||
auto room = m_connection->room(notification["room_id"].toString());
|
||||
|
||||
// If room exists, room is NOT active OR the application is NOT active, show notification
|
||||
if (room && !(room->id() == RoomManager::instance().currentRoom()->id() && QGuiApplication::applicationState() == Qt::ApplicationActive)) {
|
||||
@@ -188,7 +197,7 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
||||
|
||||
if (notification["event"]["type"] == "m.room.encrypted") {
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
auto decrypted = connection->decryptNotification(notification);
|
||||
auto decrypted = m_connection->decryptNotification(notification);
|
||||
body = decrypted["content"].toObject()["body"].toString();
|
||||
#endif
|
||||
if (body.isEmpty()) {
|
||||
@@ -225,6 +234,13 @@ void Controller::showWindow()
|
||||
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)
|
||||
{
|
||||
if (user.isEmpty() || token.isEmpty()) {
|
||||
@@ -265,6 +281,7 @@ void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
}
|
||||
|
||||
SettingsGroup("Accounts").remove(conn->userId());
|
||||
QFile(accessTokenFileName(AccountSettings(conn->userId()))).remove();
|
||||
|
||||
QKeychain::DeletePasswordJob job(qAppName());
|
||||
job.setAutoDelete(true);
|
||||
@@ -357,7 +374,15 @@ void Controller::invokeLogin()
|
||||
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
|
||||
accessToken = accessTokenLoadingJob->binaryData();
|
||||
} 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());
|
||||
@@ -373,11 +398,6 @@ void Controller::invokeLogin()
|
||||
if (error == "Unrecognised access token") {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
|
||||
logout(connection, false);
|
||||
} else if (error == "Connection closed") {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
// Failed due to network connection issue. This might happen when the homeserver is
|
||||
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
|
||||
// connect to the homeserver. In this case, we don't want to do logout().
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
logout(connection, true);
|
||||
@@ -396,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)
|
||||
{
|
||||
qDebug() << "Reading access token from the keychain for" << account.userId();
|
||||
@@ -407,6 +442,24 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const Accoun
|
||||
if (job->error() == QKeychain::Error::NoError) {
|
||||
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()) {
|
||||
case QKeychain::EntryNotFound:
|
||||
@@ -431,6 +484,22 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const Accoun
|
||||
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)
|
||||
{
|
||||
qDebug() << "Save the access token to the keychain for " << account.userId();
|
||||
@@ -445,7 +514,7 @@ bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const
|
||||
|
||||
if (job.error()) {
|
||||
qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString());
|
||||
return false;
|
||||
return saveAccessTokenToFile(account, accessToken);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -739,15 +808,11 @@ void Controller::setApplicationProxy()
|
||||
proxy.setType(QNetworkProxy::HttpProxy);
|
||||
proxy.setHostName(cfg->proxyHost());
|
||||
proxy.setPort(cfg->proxyPort());
|
||||
proxy.setUser(cfg->proxyUser());
|
||||
proxy.setPassword(cfg->proxyPassword());
|
||||
break;
|
||||
case 2: // SOCKS 5
|
||||
proxy.setType(QNetworkProxy::Socks5Proxy);
|
||||
proxy.setHostName(cfg->proxyHost());
|
||||
proxy.setPort(cfg->proxyPort());
|
||||
proxy.setUser(cfg->proxyUser());
|
||||
proxy.setPassword(cfg->proxyPassword());
|
||||
break;
|
||||
case 0: // System Default
|
||||
default:
|
||||
|
||||
@@ -68,6 +68,7 @@ public:
|
||||
|
||||
[[nodiscard]] bool supportSystemTray() const;
|
||||
|
||||
bool saveAccessTokenToFile(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
||||
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
||||
|
||||
int activeConnectionIndex() const;
|
||||
@@ -110,6 +111,7 @@ private:
|
||||
bool m_busy = false;
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
static QByteArray loadAccessTokenFromFile(const Quotient::AccountSettings &account);
|
||||
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
|
||||
|
||||
void loadSettings();
|
||||
@@ -119,7 +121,7 @@ private:
|
||||
|
||||
bool hasWindowSystem() const;
|
||||
#ifdef QUOTIENT_07
|
||||
void handleNotifications(QPointer<Quotient::Connection> connection);
|
||||
void handleNotifications();
|
||||
#endif
|
||||
|
||||
private Q_SLOTS:
|
||||
|
||||
@@ -4,17 +4,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QRegularExpression>
|
||||
#include <memory>
|
||||
#include <QRegularExpression>
|
||||
|
||||
struct CustomEmoji {
|
||||
QString name; // with :semicolons:
|
||||
QString url; // mxc://
|
||||
QRegularExpression regexp;
|
||||
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString unicode MEMBER url)
|
||||
Q_PROPERTY(QString name MEMBER name)
|
||||
};
|
||||
|
||||
class CustomEmojiModel : public QAbstractListModel
|
||||
@@ -29,7 +25,6 @@ public:
|
||||
MxcUrl = 50,
|
||||
DisplayRole = 51,
|
||||
ReplacedTextRole = 52,
|
||||
DescriptionRole = 53, // also invalid, reserved
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
@@ -70,12 +70,7 @@ void DevicesModel::logout(int index, const QString &password)
|
||||
auto job = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
|
||||
|
||||
connect(job, &BaseJob::result, this, [this, job, password, index] {
|
||||
auto onSuccess = [this, index]() {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
m_devices.remove(index);
|
||||
endRemoveRows();
|
||||
};
|
||||
if (job->error() != BaseJob::Success) {
|
||||
if (job->error() != 0) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
QJsonObject authData;
|
||||
authData["session"] = replyData["session"];
|
||||
@@ -84,9 +79,11 @@ void DevicesModel::logout(int index, const QString &password)
|
||||
QJsonObject identifier = {{"type", "m.id.user"}, {"user", Controller::instance().activeConnection()->user()->id()}};
|
||||
authData["identifier"] = identifier;
|
||||
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||
connect(innerJob, &BaseJob::success, this, onSuccess);
|
||||
} else {
|
||||
onSuccess();
|
||||
connect(innerJob, &BaseJob::success, this, [this, index]() {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
m_devices.remove(index);
|
||||
endRemoveRows();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -4,13 +4,12 @@
|
||||
#include <QVariant>
|
||||
|
||||
#include "emojimodel.h"
|
||||
#include "emojitones.h"
|
||||
#include <QDebug>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "customemojimodel.h"
|
||||
#include <KLocalizedString>
|
||||
#include <qnamespace.h>
|
||||
|
||||
EmojiModel::EmojiModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
@@ -49,8 +48,6 @@ QVariant EmojiModel::data(const QModelIndex &index, int role) const
|
||||
return QStringLiteral("invalid");
|
||||
case DisplayRole:
|
||||
return QStringLiteral("%2 :%1:").arg(emoji.shortName, emoji.unicode);
|
||||
case DescriptionRole:
|
||||
return emoji.description;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
@@ -61,19 +58,16 @@ QHash<int, QByteArray> EmojiModel::roleNames() const
|
||||
return {{ShortNameRole, "shortName"}, {UnicodeRole, "unicode"}};
|
||||
}
|
||||
|
||||
QMultiHash<QString, QVariant> EmojiModel::_tones = {
|
||||
#include "emojitones.h"
|
||||
};
|
||||
|
||||
QVariantList EmojiModel::history() const
|
||||
{
|
||||
return m_settings.value("Editor/emojis", QVariantList()).toList();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -88,6 +82,7 @@ QVariantList EmojiModel::filterModelNoCustom(const QString &filter, bool limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -115,28 +110,12 @@ QVariantList EmojiModel::emojis(Category category) const
|
||||
if (category == 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];
|
||||
}
|
||||
|
||||
QVariantList EmojiModel::tones(const QString &baseEmoji) const
|
||||
{
|
||||
if (baseEmoji.endsWith("tone")) {
|
||||
return EmojiTones::_tones.values(baseEmoji.split(":")[0]);
|
||||
}
|
||||
return EmojiTones::_tones.values(baseEmoji);
|
||||
return _tones.values(baseEmoji);
|
||||
}
|
||||
|
||||
QHash<EmojiModel::Category, QVariantList> EmojiModel::_emojis;
|
||||
@@ -144,11 +123,21 @@ QHash<EmojiModel::Category, QVariantList> EmojiModel::_emojis;
|
||||
QVariantList EmojiModel::categories() const
|
||||
{
|
||||
return QVariantList{
|
||||
// {QVariantMap{
|
||||
// {"category", EmojiModel::Search},
|
||||
// {"name", i18nc("Search for emojis", "Search")},
|
||||
// {"emoji", QStringLiteral("🔎")},
|
||||
// }},
|
||||
{QVariantMap{
|
||||
{"category", EmojiModel::HistoryNoCustom},
|
||||
{"category", EmojiModel::History},
|
||||
{"name", i18nc("Previously used emojis", "History")},
|
||||
{"emoji", QStringLiteral("⌛️")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{"category", EmojiModel::Custom},
|
||||
{"name", i18nc("'Custom' is a category of emoji", "Custom")},
|
||||
{"emoji", QStringLiteral("😏")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{"category", EmojiModel::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;
|
||||
}
|
||||
@@ -8,18 +8,12 @@
|
||||
#include <QSettings>
|
||||
|
||||
struct Emoji {
|
||||
Emoji(QString unicode, QString shortname, bool isCustom = false)
|
||||
: unicode(std::move(unicode))
|
||||
, shortName(std::move(shortname))
|
||||
Emoji(QString u, QString s, bool isCustom = false)
|
||||
: unicode(std::move(std::move(u)))
|
||||
, shortName(std::move(std::move(s)))
|
||||
, isCustom(isCustom)
|
||||
{
|
||||
}
|
||||
Emoji(QString unicode, QString shortname, QString description)
|
||||
: unicode(std::move(unicode))
|
||||
, shortName(std::move(shortname))
|
||||
, description(std::move(description))
|
||||
{
|
||||
}
|
||||
Emoji() = default;
|
||||
|
||||
friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
|
||||
@@ -39,13 +33,11 @@ struct Emoji {
|
||||
|
||||
QString unicode;
|
||||
QString shortName;
|
||||
QString description;
|
||||
bool isCustom = false;
|
||||
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString unicode MEMBER unicode)
|
||||
Q_PROPERTY(QString shortName MEMBER shortName)
|
||||
Q_PROPERTY(QString description MEMBER description)
|
||||
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 categories READ categories CONSTANT)
|
||||
Q_PROPERTY(QVariantList categoriesWithCustom READ categoriesWithCustom CONSTANT)
|
||||
|
||||
public:
|
||||
static EmojiModel &instance()
|
||||
@@ -72,16 +63,13 @@ public:
|
||||
InvalidRole = 50,
|
||||
DisplayRole = 51,
|
||||
ReplacedTextRole = 52,
|
||||
DescriptionRole = 53,
|
||||
};
|
||||
Q_ENUM(RoleNames);
|
||||
|
||||
enum Category {
|
||||
Custom,
|
||||
Search,
|
||||
SearchNoCustom,
|
||||
History,
|
||||
HistoryNoCustom,
|
||||
Smileys,
|
||||
People,
|
||||
Nature,
|
||||
@@ -101,14 +89,11 @@ public:
|
||||
|
||||
Q_INVOKABLE QVariantList history() const;
|
||||
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 tones(const QString &baseEmoji) const;
|
||||
|
||||
QVariantList categories() const;
|
||||
QVariantList categoriesWithCustom() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void historyChanged();
|
||||
@@ -118,6 +103,7 @@ public Q_SLOTS:
|
||||
|
||||
private:
|
||||
static QHash<Category, QVariantList> _emojis;
|
||||
static QMultiHash<QString, QVariant> _tones;
|
||||
|
||||
// TODO: Port away from QSettings
|
||||
QSettings m_settings;
|
||||
3706
src/emojis.h
3706
src/emojis.h
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
||||
// SPDX-FileCopyrightText: None
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "emojitones.h"
|
||||
#include "models/emojimodel.h"
|
||||
|
||||
QMultiHash<QString, QVariant> EmojiTones::_tones = {
|
||||
#include "emojitones_data.h"
|
||||
};
|
||||
1794
src/emojitones.h
1794
src/emojitones.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,9 +15,7 @@
|
||||
KeywordNotificationRuleModel::KeywordNotificationRuleModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
if (Controller::instance().activeConnection()) {
|
||||
controllerConnectionChanged();
|
||||
}
|
||||
controllerConnectionChanged();
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &KeywordNotificationRuleModel::controllerConnectionChanged);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
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,
|
||||
// rather than with the full mxid, as that would lead to an invalid user.
|
||||
auto username = m_matrixId.mid(1, m_matrixId.indexOf(":") - 1);
|
||||
m_connection->loginWithPassword(username, m_password, m_deviceName, QString());
|
||||
QStringRef username(&m_matrixId, 1, m_matrixId.indexOf(":") - 1);
|
||||
m_connection->loginWithPassword(username.toString(), m_password, m_deviceName, QString());
|
||||
}
|
||||
|
||||
bool Login::supportsPassword() const
|
||||
@@ -181,8 +183,8 @@ void Login::loginWithSso()
|
||||
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() {
|
||||
SsoSession *session = m_connection->prepareForSso(m_deviceName);
|
||||
m_ssoUrl = session->ssoUrl();
|
||||
Q_EMIT ssoUrlChanged();
|
||||
});
|
||||
Q_EMIT ssoUrlChanged();
|
||||
}
|
||||
|
||||
bool Login::testing() const
|
||||
|
||||
93
src/main.cpp
93
src/main.cpp
@@ -42,29 +42,19 @@
|
||||
#include "blurhashimageprovider.h"
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "clipboard.h"
|
||||
#include "collapsestateproxymodel.h"
|
||||
#include "controller.h"
|
||||
#include "customemojimodel.h"
|
||||
#include "devicesmodel.h"
|
||||
#include "emojimodel.h"
|
||||
#include "filetypesingleton.h"
|
||||
#include "joinrulesevent.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "keywordnotificationrulemodel.h"
|
||||
#include "login.h"
|
||||
#include "matriximageprovider.h"
|
||||
#include "models/collapsestateproxymodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/devicesmodel.h"
|
||||
#include "models/emojimodel.h"
|
||||
#include "models/keywordnotificationrulemodel.h"
|
||||
#include "models/messageeventmodel.h"
|
||||
#include "models/messagefiltermodel.h"
|
||||
#include "models/publicroomlistmodel.h"
|
||||
#include "models/roomlistmodel.h"
|
||||
#include "models/searchmodel.h"
|
||||
#include "models/serverlistmodel.h"
|
||||
#include "models/sortfilterroomlistmodel.h"
|
||||
#include "models/sortfilterspacelistmodel.h"
|
||||
#include "models/userdirectorylistmodel.h"
|
||||
#include "models/userfiltermodel.h"
|
||||
#include "models/userlistmodel.h"
|
||||
#include "models/webshortcutmodel.h"
|
||||
#include "messageeventmodel.h"
|
||||
#include "messagefiltermodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
@@ -72,9 +62,17 @@
|
||||
#ifdef QUOTIENT_07
|
||||
#include "pollhandler.h"
|
||||
#endif
|
||||
#include "publicroomlistmodel.h"
|
||||
#include "roomlistmodel.h"
|
||||
#include "roommanager.h"
|
||||
#include "serverlistmodel.h"
|
||||
#include "sortfilterroomlistmodel.h"
|
||||
#include "sortfilterspacelistmodel.h"
|
||||
#include "spacehierarchycache.h"
|
||||
#include "urlhelper.h"
|
||||
#include "userdirectorylistmodel.h"
|
||||
#include "userlistmodel.h"
|
||||
#include "webshortcutmodel.h"
|
||||
#include "windowcontroller.h"
|
||||
#ifdef QUOTIENT_07
|
||||
#include <keyverificationsession.h>
|
||||
@@ -82,19 +80,15 @@
|
||||
#ifdef HAVE_COLORSCHEME
|
||||
#include "colorschemer.h"
|
||||
#endif
|
||||
#include "models/completionmodel.h"
|
||||
#include "models/statemodel.h"
|
||||
#include "completionmodel.h"
|
||||
#include "neochatuser.h"
|
||||
#include "statemodel.h"
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
#include "runner.h"
|
||||
#include <QDBusConnection>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
|
||||
@@ -225,7 +219,6 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
||||
qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel");
|
||||
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<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
|
||||
qmlRegisterType<ServerListModel>("org.kde.neochat", 1, 0, "ServerListModel");
|
||||
@@ -235,7 +228,6 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
|
||||
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
||||
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
||||
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
||||
#ifdef QUOTIENT_07
|
||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||
#endif
|
||||
@@ -273,32 +265,6 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
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));
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
|
||||
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
|
||||
@@ -328,6 +294,31 @@ int main(int argc, char *argv[])
|
||||
QDBusConnection::sessionBus().registerObject("/RoomRunner", &runner, QDBusConnection::ExportScriptableContents);
|
||||
#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);
|
||||
|
||||
WindowController::instance().setWindow(window);
|
||||
|
||||
@@ -66,8 +66,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[AuthorDisplayNameRole] = "authorDisplayName";
|
||||
roles[IsNameChangeRole] = "isNameChange";
|
||||
roles[IsAvatarChangeRole] = "isAvatarChange";
|
||||
roles[IsRedactedRole] = "isRedacted";
|
||||
roles[GenericDisplayRole] = "genericDisplay";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -440,7 +438,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
case EventTypeRole:
|
||||
return DelegateType::ReadMarker;
|
||||
case TimeRole: {
|
||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime().toLocalTime();
|
||||
const QDateTime eventDate = data(index(m_lastReadEventIndex.row() + 1, 0), TimeRole).toDateTime();
|
||||
const KFormat format;
|
||||
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
||||
}
|
||||
@@ -463,14 +461,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return m_currentRoom->eventToString(evt, Qt::RichText);
|
||||
}
|
||||
|
||||
if (role == GenericDisplayRole) {
|
||||
if (evt.isRedacted()) {
|
||||
return i18n("<i>[This message was deleted]</i>");
|
||||
}
|
||||
|
||||
return m_currentRoom->eventToGenericString(evt);
|
||||
}
|
||||
|
||||
if (role == FormattedBodyRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
if (e->hasTextContent() && e->mimeType().name() != "text/plain") {
|
||||
@@ -904,9 +894,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (role == IsRedactedRole) {
|
||||
return evt.isRedacted();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -45,7 +45,6 @@ public:
|
||||
AnnotationRole,
|
||||
UserMarkerRole,
|
||||
FormattedBodyRole,
|
||||
GenericDisplayRole,
|
||||
|
||||
MimeTypeRole,
|
||||
FileMimetypeIcon,
|
||||
@@ -72,7 +71,6 @@ public:
|
||||
AuthorDisplayNameRole,
|
||||
IsNameChangeRole,
|
||||
IsAvatarChangeRole,
|
||||
IsRedactedRole,
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
@@ -25,10 +25,6 @@ MessageFilterModel::MessageFilterModel(QObject *parent)
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowDeletedMessagesChanged, this, [this] {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
}
|
||||
|
||||
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()) {
|
||||
return false;
|
||||
}
|
||||
if (index.data(MessageEventModel::IsRedactedRole).toBool() && !NeoChatConfig::self()->showDeletedMessages()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (specialMarks == EventStatus::Hidden || specialMarks == EventStatus::Replaced) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto eventType = index.data(MessageEventModel::EventTypeRole).toInt();
|
||||
const QString eventType = index.data(MessageEventModel::EventTypeRole).toString();
|
||||
|
||||
if (eventType == MessageEventModel::Other) {
|
||||
return false;
|
||||
@@ -1,134 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "collapsestateproxymodel.h"
|
||||
#include "messageeventmodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is the first state in a block, show it. TODO hidden events?
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
||||
}
|
||||
|
||||
QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == AggregateDisplayRole) {
|
||||
return aggregateEventToString(mapToSource(index).row());
|
||||
} else if (role == StateEventsRole) {
|
||||
return stateEventsList(mapToSource(index).row());
|
||||
} else if (role == AuthorListRole) {
|
||||
return authorList(mapToSource(index).row());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
||||
{
|
||||
auto roles = sourceModel()->roleNames();
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
roles[StateEventsRole] = "stateEvents";
|
||||
roles[AuthorListRole] = "authorList";
|
||||
return roles;
|
||||
}
|
||||
|
||||
QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
{
|
||||
QStringList parts;
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||
QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||
uniqueAuthors.append(nextAuthor);
|
||||
}
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
parts.sort(); // Sort them so that all identical events can be collected.
|
||||
if (!parts.isEmpty()) {
|
||||
QStringList chunks;
|
||||
while (!parts.isEmpty()) {
|
||||
chunks += QString();
|
||||
int count = 1;
|
||||
auto part = parts.takeFirst();
|
||||
chunks.last() += part;
|
||||
while (!parts.isEmpty() && parts.first() == part) {
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
if (count > 1 && uniqueAuthors.length() == 1) {
|
||||
chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count);
|
||||
}
|
||||
}
|
||||
chunks.removeDuplicates();
|
||||
QString text = "<style>a {text-decoration: none;}</style>"; // There can be links in the event text so make sure all are styled.
|
||||
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()["id"].toString(),
|
||||
uniqueAuthors[0].toMap()["color"].toString(),
|
||||
uniqueAuthors[0].toMap()["displayName"].toString());
|
||||
text += userText;
|
||||
text += chunks.takeFirst();
|
||||
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
text += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
return text;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
||||
{
|
||||
QVariantList stateEvents;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
auto nextState = QVariantMap{
|
||||
{"author", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
|
||||
{"authorDisplayName", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
|
||||
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
};
|
||||
stateEvents.append(nextState);
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
|
||||
QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return uniqueAuthors;
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "searchmodel.h"
|
||||
#include "messageeventmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include <KLocalizedString>
|
||||
#include <connection.h>
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
#include <csapi/search.h>
|
||||
#endif
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
// TODO search only in the current room
|
||||
|
||||
SearchModel::SearchModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString SearchModel::searchText() const
|
||||
{
|
||||
return m_searchText;
|
||||
}
|
||||
|
||||
void SearchModel::setSearchText(const QString &searchText)
|
||||
{
|
||||
m_searchText = searchText;
|
||||
Q_EMIT searchTextChanged();
|
||||
}
|
||||
|
||||
void SearchModel::search()
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
Q_ASSERT(m_connection);
|
||||
setSearching(true);
|
||||
if (m_job) {
|
||||
m_job->abandon();
|
||||
m_job = nullptr;
|
||||
}
|
||||
|
||||
SearchJob::RoomEventsCriteria criteria{
|
||||
m_searchText,
|
||||
{},
|
||||
RoomEventFilter{
|
||||
.rooms = {m_room->id()},
|
||||
},
|
||||
"recent",
|
||||
SearchJob::IncludeEventContext{3, 3, true},
|
||||
false,
|
||||
none,
|
||||
};
|
||||
|
||||
auto job = m_connection->callApi<SearchJob>(SearchJob::Categories{criteria});
|
||||
m_job = job;
|
||||
connect(job, &BaseJob::finished, this, [=] {
|
||||
beginResetModel();
|
||||
m_result = job->searchCategories().roomEvents;
|
||||
endResetModel();
|
||||
setSearching(false);
|
||||
m_job = nullptr;
|
||||
// TODO error handling
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
Connection *SearchModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void SearchModel::setConnection(Connection *connection)
|
||||
{
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
}
|
||||
|
||||
QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
auto row = index.row();
|
||||
const auto &event = *m_result->results[row].result;
|
||||
switch (role) {
|
||||
case DisplayRole:
|
||||
return m_room->eventToString(*m_result->results[row].result);
|
||||
case ShowAuthorRole:
|
||||
return true;
|
||||
case AuthorRole:
|
||||
return QVariantMap{
|
||||
{"isLocalUser", event.senderId() == m_room->localUser()->id()},
|
||||
{"id", event.senderId()},
|
||||
{"avatarMediaId", m_connection->user(event.senderId())->avatarMediaId(m_room)},
|
||||
{"avatarUrl", m_connection->user(event.senderId())->avatarUrl(m_room)},
|
||||
{"displayName", m_connection->user(event.senderId())->displayname(m_room)},
|
||||
{"display", m_connection->user(event.senderId())->name()},
|
||||
{"color", dynamic_cast<NeoChatUser *>(m_connection->user(event.senderId()))->color()},
|
||||
{"object", QVariant::fromValue(m_connection->user(event.senderId()))},
|
||||
};
|
||||
case ShowSectionRole:
|
||||
if (row == 0) {
|
||||
return true;
|
||||
}
|
||||
return event.originTimestamp().date() != m_result->results[row - 1].result->originTimestamp().date();
|
||||
case SectionRole:
|
||||
return renderDate(event.originTimestamp());
|
||||
case TimeRole:
|
||||
return event.originTimestamp();
|
||||
}
|
||||
return MessageEventModel::DelegateType::Message;
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
int SearchModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
if (m_result.has_value()) {
|
||||
return m_result->results.size();
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> SearchModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{EventTypeRole, "eventType"},
|
||||
{DisplayRole, "display"},
|
||||
{AuthorRole, "author"},
|
||||
{ShowSectionRole, "showSection"},
|
||||
{SectionRole, "section"},
|
||||
{TimeRole, "time"},
|
||||
{ShowAuthorRole, "showAuthor"},
|
||||
};
|
||||
}
|
||||
|
||||
NeoChatRoom *SearchModel::room() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void SearchModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
// TODO deduplicate with messageeventmodel
|
||||
QString renderDate(const QDateTime ×tamp)
|
||||
{
|
||||
auto date = timestamp.toLocalTime().date();
|
||||
if (date == QDate::currentDate()) {
|
||||
return i18n("Today");
|
||||
}
|
||||
if (date == QDate::currentDate().addDays(-1)) {
|
||||
return i18n("Yesterday");
|
||||
}
|
||||
if (date == QDate::currentDate().addDays(-2)) {
|
||||
return i18n("The day before yesterday");
|
||||
}
|
||||
if (date > QDate::currentDate().addDays(-7)) {
|
||||
return date.toString("dddd");
|
||||
}
|
||||
|
||||
return QLocale::system().toString(date, QLocale::ShortFormat);
|
||||
}
|
||||
|
||||
bool SearchModel::searching() const
|
||||
{
|
||||
return m_searching;
|
||||
}
|
||||
|
||||
void SearchModel::setSearching(bool searching)
|
||||
{
|
||||
m_searching = searching;
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QString>
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
#include <csapi/search.h>
|
||||
#endif
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
}
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
class SearchModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
|
||||
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
DisplayRole = Qt::DisplayRole,
|
||||
EventTypeRole,
|
||||
ShowAuthorRole,
|
||||
AuthorRole,
|
||||
ShowSectionRole,
|
||||
SectionRole,
|
||||
TimeRole,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
SearchModel(QObject *parent = nullptr);
|
||||
|
||||
QString searchText() const;
|
||||
void setSearchText(const QString &searchText);
|
||||
|
||||
Quotient::Connection *connection() const;
|
||||
void setConnection(Quotient::Connection *connection);
|
||||
|
||||
NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
Q_INVOKABLE void search();
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
bool searching() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void searchTextChanged();
|
||||
void connectionChanged();
|
||||
void roomChanged();
|
||||
void searchingChanged();
|
||||
|
||||
private:
|
||||
void setSearching(bool searching);
|
||||
|
||||
QString m_searchText;
|
||||
Quotient::Connection *m_connection = nullptr;
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
#ifdef QUOTIENT_07
|
||||
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
|
||||
Quotient::SearchJob *m_job = nullptr;
|
||||
#endif
|
||||
bool m_searching = false;
|
||||
};
|
||||
|
||||
QString renderDate(const QDateTime &dateTime);
|
||||
@@ -1,28 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "userfiltermodel.h"
|
||||
|
||||
#include "userlistmodel.h"
|
||||
|
||||
bool UserFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
Q_UNUSED(sourceParent);
|
||||
if (m_filterText.length() < 1) {
|
||||
return false;
|
||||
}
|
||||
return sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::NameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
|| sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::UserIdRole).toString().contains(m_filterText, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
QString UserFilterModel::filterText() const
|
||||
{
|
||||
return m_filterText;
|
||||
}
|
||||
|
||||
void UserFilterModel::setFilterText(const QString &filterText)
|
||||
{
|
||||
m_filterText = filterText;
|
||||
Q_EMIT filterTextChanged();
|
||||
invalidateFilter();
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class UserFilterModel
|
||||
*
|
||||
* This class creates a custom QSortFilterProxyModel for filtering a users by either
|
||||
* display name or matrix ID. The filter can accept a full matrix id i.e. example:kde.org
|
||||
* to separate between accounts on different servers with similar names.
|
||||
*/
|
||||
class UserFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief This property hold the text of the filter.
|
||||
*
|
||||
* The text is either a desired display name or matrix id.
|
||||
*/
|
||||
Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Custom filter function checking boith the display name and matrix ID.
|
||||
*
|
||||
* @note The filter cannot be modified and will always use the same filter properties.
|
||||
*/
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
QString filterText() const;
|
||||
void setFilterText(const QString &filterText);
|
||||
|
||||
Q_SIGNALS:
|
||||
void filterTextChanged();
|
||||
|
||||
private:
|
||||
QString m_filterText;
|
||||
};
|
||||
@@ -7,7 +7,6 @@ Name[ca]=NeoChat
|
||||
Name[ca@valencia]=NeoChat
|
||||
Name[cs]=NeoChat
|
||||
Name[de]=NeoChat
|
||||
Name[el]=NeoChat
|
||||
Name[en_GB]=NeoChat
|
||||
Name[es]=NeoChat
|
||||
Name[eu]=NeoChat
|
||||
@@ -28,7 +27,6 @@ Name[pl]=NeoChat
|
||||
Name[pt]=NeoChat
|
||||
Name[pt_BR]=NeoChat
|
||||
Name[ro]=NeoChat
|
||||
Name[ru]=NeoChat
|
||||
Name[sk]=NeoChat
|
||||
Name[sl]=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[cs]=Klient pro decentralizovaný komunikační protokol matrix
|
||||
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[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado
|
||||
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_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado
|
||||
Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată
|
||||
Comment[ru]=Клиент для Matrix — децентрализованного коммуникационного протокола
|
||||
Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol
|
||||
Comment[sl]=Odjemalec za decentralizirani komunikacijski protokol matrix
|
||||
Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokollet
|
||||
@@ -84,7 +80,6 @@ Name[ca]=Missatge nou
|
||||
Name[ca@valencia]=Missatge nou
|
||||
Name[cs]=Nová zpráva
|
||||
Name[de]=Neue Nachricht
|
||||
Name[el]=Νέο μήνυμα
|
||||
Name[en_GB]=New message
|
||||
Name[es]=Nuevo mensaje
|
||||
Name[eu]=Mezu berria
|
||||
@@ -105,7 +100,6 @@ Name[pl]=Nowa wiadomość
|
||||
Name[pt]=Nova mensagem
|
||||
Name[pt_BR]=Nova mensagem
|
||||
Name[ro]=Mesaj nou
|
||||
Name[ru]=Новое сообщение
|
||||
Name[sk]=Nová správa
|
||||
Name[sl]=Novo sporočilo
|
||||
Name[sv]=Nytt meddelande
|
||||
@@ -119,8 +113,7 @@ Comment[ar]=توجد رسالة جديدة
|
||||
Comment[az]=Yeni ismarıc var
|
||||
Comment[ca]=Hi ha un missatge nou
|
||||
Comment[ca@valencia]=Hi ha un missatge nou
|
||||
Comment[de]=Es gibt eine neue Nachricht
|
||||
Comment[el]=Υπάρχει ένα νέο μήνυμα
|
||||
Comment[de]=Es ist eine neue Nachricht vorhanden
|
||||
Comment[en_GB]=There is a new message
|
||||
Comment[es]=Hay un mensaje nuevo
|
||||
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_BR]=Existe uma nova mensagem
|
||||
Comment[ro]=Este un mesaj nou
|
||||
Comment[ru]=Доступно новое сообщение
|
||||
Comment[sk]=Je nová správa
|
||||
Comment[sl]=Prišlo je novo sporočilo
|
||||
Comment[sv]=Det finns ett nytt meddelande
|
||||
@@ -160,7 +152,6 @@ Name[ca]=Invitació nova
|
||||
Name[ca@valencia]=Invitació nova
|
||||
Name[cs]=Nová pozvánka
|
||||
Name[de]=Neue Einladung
|
||||
Name[el]=Νέα πρόσκληση
|
||||
Name[en_GB]=New Invitation
|
||||
Name[es]=Nueva invitación
|
||||
Name[eu]=Gonbidapen berria
|
||||
@@ -177,7 +168,6 @@ Name[pa]=ਨਵਾਂ ਸੱਦਾ
|
||||
Name[pl]=Nowe zaproszenie
|
||||
Name[pt]=Novo Convite
|
||||
Name[pt_BR]=Novo convite
|
||||
Name[ru]=Новое приглашение
|
||||
Name[sl]=Novo povabilo
|
||||
Name[sv]=Ny inbjudan
|
||||
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[cs]=Máte novou pozvánku do místnosti
|
||||
Comment[de]=Es gibt eine neue Einladung zu einem Raum
|
||||
Comment[el]=Υπάρχει μια νέα πρόσκληση σε μια αίθουσα
|
||||
Comment[en_GB]=There is a new invitation to a room
|
||||
Comment[es]=Hay una nueva invitación a una sala
|
||||
Comment[eu]=Gela baterako gonbidapen berri bat dago
|
||||
@@ -208,7 +197,6 @@ Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
|
||||
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
|
||||
Comment[pt]=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[sv]=Det finns en ny inbjudan till ett rum
|
||||
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
<label>Background transparency value</label>
|
||||
<default>0.3</default>
|
||||
</entry>
|
||||
<entry name="ShowNotifications" type="bool">
|
||||
<label>Show notifications</label>
|
||||
<default>true</default>
|
||||
</entry>
|
||||
<entry name="MergeRoomList" type="bool">
|
||||
<label>Merge Room Lists</label>
|
||||
<default>false</default>
|
||||
@@ -58,9 +62,6 @@
|
||||
<label>Enable developer tools</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="LastSaveDirectory" type="String">
|
||||
<label>Directory last used for saving a file</label>
|
||||
</entry>
|
||||
</group>
|
||||
<group name="Timeline">
|
||||
<entry name="ShowAvatarInTimeline" type="bool">
|
||||
@@ -68,11 +69,7 @@
|
||||
<default>true</default>
|
||||
</entry>
|
||||
<entry name="CompactLayout" type="bool">
|
||||
<label>Use a compact chat layout</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="CompactRoomList" type="bool">
|
||||
<label>Use a compact room list layout</label>
|
||||
<label>Use a compact layout</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="ShowRename" type="bool">
|
||||
@@ -83,10 +80,6 @@
|
||||
<label>Show avatar update events in the timeline</label>
|
||||
<default>true</default>
|
||||
</entry>
|
||||
<entry name="ShowDeletedMessages" type="bool">
|
||||
<label>Show deleted messages in the timeline</label>
|
||||
<default>true</default>
|
||||
</entry>
|
||||
<entry name="ShowLinkPreview" type="bool">
|
||||
<label>Show preview of the links in the chat messages</label>
|
||||
</entry>
|
||||
@@ -133,14 +126,6 @@
|
||||
<label>The port number of the proxy</label>
|
||||
<default>1080</default>
|
||||
</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>
|
||||
</kcfg>
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
#include <QMediaPlayer>
|
||||
|
||||
#include <qcoro/qcorosignal.h>
|
||||
#include <qcoro/task.h>
|
||||
|
||||
#include <connection.h>
|
||||
#include <csapi/directory.h>
|
||||
#include <csapi/pushrules.h>
|
||||
#include <csapi/redaction.h>
|
||||
#include <csapi/report_content.h>
|
||||
@@ -31,9 +31,6 @@
|
||||
#include <events/roompowerlevelsevent.h>
|
||||
#include <events/simplestateevents.h>
|
||||
#include <jobs/downloadfilejob.h>
|
||||
#ifndef QUOTIENT_07
|
||||
#include <joinstate.h>
|
||||
#endif
|
||||
#include <qt_connection_util.h>
|
||||
|
||||
#include "controller.h"
|
||||
@@ -97,7 +94,6 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
connect(this, &Room::changed, this, [this] {
|
||||
Q_EMIT canEncryptRoomChanged();
|
||||
});
|
||||
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
||||
}
|
||||
|
||||
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);
|
||||
url.setScheme("file");
|
||||
QFileInfo fileInfo(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||
QFileInfo fileInfo(url.toLocalFile());
|
||||
EventContent::TypedBase *content;
|
||||
if (mime.name().startsWith("image/")) {
|
||||
QImage image(url.toLocalFile());
|
||||
@@ -333,7 +328,7 @@ QString NeoChatRoom::subtitleText()
|
||||
{
|
||||
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 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 bold2("(\\*|_)(?=\\S)([^\\r]*\\S)\\1");
|
||||
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());
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -499,14 +501,18 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
plainBody = e.plainBody();
|
||||
}
|
||||
|
||||
if (prettyPrint) {
|
||||
if (removeReply) {
|
||||
plainBody.remove(utils::removeReplyRegex);
|
||||
}
|
||||
return Quotient::prettyPrint(plainBody);
|
||||
}
|
||||
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;
|
||||
},
|
||||
@@ -604,10 +610,8 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
} else {
|
||||
return i18n("self-banned from the room");
|
||||
}
|
||||
case MembershipType::Knock: {
|
||||
QString reason(e.contentJson()["reason"_ls].toString().toHtmlEscaped());
|
||||
return reason.isEmpty() ? i18n("requested an invite") : i18n("requested an invite with reason: %1", reason);
|
||||
}
|
||||
case MembershipType::Knock:
|
||||
return i18n("requested an invite");
|
||||
default:;
|
||||
}
|
||||
return i18n("made something unknown");
|
||||
@@ -658,135 +662,6 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
|
||||
QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
return switchOnType(
|
||||
#else
|
||||
return visit(
|
||||
#endif
|
||||
evt,
|
||||
[](const RoomMessageEvent &e) {
|
||||
Q_UNUSED(e)
|
||||
return i18n("sent a message");
|
||||
},
|
||||
[](const StickerEvent &e) {
|
||||
Q_UNUSED(e)
|
||||
return i18n("sent a sticker");
|
||||
},
|
||||
[](const RoomMemberEvent &e) {
|
||||
switch (e.membership()) {
|
||||
case MembershipType::Invite:
|
||||
if (e.repeatsState()) {
|
||||
return i18n("reinvited someone to the room");
|
||||
}
|
||||
Q_FALLTHROUGH();
|
||||
case MembershipType::Join: {
|
||||
QString text{};
|
||||
// Part 1: invites and joins
|
||||
if (e.repeatsState()) {
|
||||
text = i18n("joined the room (repeated)");
|
||||
} else if (e.changesMembership()) {
|
||||
text = e.membership() == MembershipType::Invite ? i18n("invited someone to the room") : i18n("joined the room");
|
||||
}
|
||||
if (!text.isEmpty()) {
|
||||
return text;
|
||||
}
|
||||
// Part 2: profile changes of joined members
|
||||
if (e.isRename()) {
|
||||
if (e.displayName().isEmpty()) {
|
||||
text = i18nc("their refers to a singular user", "cleared their display name");
|
||||
} else {
|
||||
text = i18nc("their refers to a singular user", "changed their display name");
|
||||
}
|
||||
}
|
||||
if (e.isAvatarUpdate()) {
|
||||
if (!text.isEmpty()) {
|
||||
text += i18n(" and ");
|
||||
}
|
||||
if (e.avatarUrl().isEmpty()) {
|
||||
text += i18nc("their refers to a singular user", "cleared their avatar");
|
||||
#ifdef QUOTIENT_07
|
||||
} else if (!e.prevContent()->avatarUrl) {
|
||||
#else
|
||||
} else if (e.prevContent()->avatarUrl.isEmpty()) {
|
||||
#endif
|
||||
text += i18n("set an avatar");
|
||||
} else {
|
||||
text += i18nc("their refers to a singular user", "updated their avatar");
|
||||
}
|
||||
}
|
||||
if (text.isEmpty()) {
|
||||
text = i18nc("<user> changed nothing", "changed nothing");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
case MembershipType::Leave:
|
||||
if (e.prevContent() && e.prevContent()->membership == MembershipType::Invite) {
|
||||
return (e.senderId() != e.userId()) ? i18n("withdrew a user's invitation") : i18n("rejected the invitation");
|
||||
}
|
||||
|
||||
if (e.prevContent() && e.prevContent()->membership == MembershipType::Ban) {
|
||||
return (e.senderId() != e.userId()) ? i18n("unbanned a user") : i18n("self-unbanned");
|
||||
}
|
||||
return (e.senderId() != e.userId()) ? i18n("put a user out of the room") : i18n("left the room");
|
||||
case MembershipType::Ban:
|
||||
if (e.senderId() != e.userId()) {
|
||||
return i18n("banned a user from the room");
|
||||
} else {
|
||||
return i18n("self-banned from the room");
|
||||
}
|
||||
case MembershipType::Knock: {
|
||||
return i18n("requested an invite");
|
||||
}
|
||||
default:;
|
||||
}
|
||||
return i18n("made something unknown");
|
||||
},
|
||||
[](const RoomCanonicalAliasEvent &e) {
|
||||
return (e.alias().isEmpty()) ? i18n("cleared the room main alias") : i18n("set the room main alias");
|
||||
},
|
||||
[](const RoomNameEvent &e) {
|
||||
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name");
|
||||
},
|
||||
[](const RoomTopicEvent &e) {
|
||||
return (e.topic().isEmpty()) ? i18n("cleared the topic") : i18n("set the topic");
|
||||
},
|
||||
[](const RoomAvatarEvent &) {
|
||||
return i18n("changed the room avatar");
|
||||
},
|
||||
[](const EncryptionEvent &) {
|
||||
return i18n("activated End-to-End Encryption");
|
||||
},
|
||||
[](const RoomCreateEvent &e) {
|
||||
return e.isUpgrade() ? i18n("upgraded the room version") : i18n("created the room");
|
||||
},
|
||||
[](const RoomPowerLevelsEvent &) {
|
||||
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
||||
},
|
||||
[](const StateEventBase &e) {
|
||||
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
||||
return i18n("changed the server access control lists for this room");
|
||||
}
|
||||
if (e.matrixType() == QLatin1String("im.vector.modular.widgets")) {
|
||||
if (e.fullJson()["unsigned"]["prev_content"].toObject().isEmpty()) {
|
||||
return i18n("added a widget");
|
||||
}
|
||||
if (e.contentJson().isEmpty()) {
|
||||
return i18n("removed a widget");
|
||||
}
|
||||
return i18n("configured a widget");
|
||||
}
|
||||
return i18n("updated the state");
|
||||
},
|
||||
#ifdef QUOTIENT_07
|
||||
[](const PollStartEvent &e) {
|
||||
return i18n("started a poll");
|
||||
},
|
||||
#endif
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
|
||||
void NeoChatRoom::changeAvatar(const QUrl &localFile)
|
||||
{
|
||||
const auto job = connection()->uploadFile(localFile.toLocalFile());
|
||||
@@ -1075,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.
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
QStringList events;
|
||||
@@ -1626,17 +1202,6 @@ void NeoChatRoom::setChatBoxText(const QString &text)
|
||||
Q_EMIT chatBoxTextChanged();
|
||||
}
|
||||
|
||||
QString NeoChatRoom::editText() const
|
||||
{
|
||||
return m_editText;
|
||||
}
|
||||
|
||||
void NeoChatRoom::setEditText(const QString &text)
|
||||
{
|
||||
m_editText = text;
|
||||
Q_EMIT editTextChanged();
|
||||
}
|
||||
|
||||
QString NeoChatRoom::chatBoxReplyId() const
|
||||
{
|
||||
return m_chatBoxReplyId;
|
||||
@@ -1713,11 +1278,6 @@ QVector<Mention> *NeoChatRoom::mentions()
|
||||
return &m_mentions;
|
||||
}
|
||||
|
||||
QVector<Mention> *NeoChatRoom::editMentions()
|
||||
{
|
||||
return &m_editMentions;
|
||||
}
|
||||
|
||||
QString NeoChatRoom::savedText() const
|
||||
{
|
||||
return m_savedText;
|
||||
@@ -1775,57 +1335,3 @@ void NeoChatRoom::download(const QString &eventId, const QUrl &localFilename)
|
||||
job->start();
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qobjectdefs.h>
|
||||
#include <room.h>
|
||||
|
||||
#include <QCache>
|
||||
@@ -12,8 +11,8 @@
|
||||
|
||||
#include <qcoro/task.h>
|
||||
|
||||
#include "neochatuser.h"
|
||||
#include "pollhandler.h"
|
||||
class PollHandler;
|
||||
class NeoChatUser;
|
||||
|
||||
class PushNotificationState : public QObject
|
||||
{
|
||||
@@ -47,43 +46,15 @@ class NeoChatRoom : public Quotient::Room
|
||||
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
|
||||
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
||||
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(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
|
||||
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(PushNotificationState::State pushNotificationState MEMBER m_currentPushNotificationState WRITE setPushNotificationState NOTIFY
|
||||
pushNotificationStateChanged)
|
||||
|
||||
// Due to problems with QTextDocument, unlike the other properties here, chatBoxText is *not* used to store the text when switching rooms
|
||||
Q_PROPERTY(QString chatBoxText READ chatBoxText WRITE setChatBoxText NOTIFY chatBoxTextChanged)
|
||||
|
||||
/**
|
||||
* @brief The text for any message currently being edited in the room.
|
||||
*/
|
||||
Q_PROPERTY(QString editText READ editText WRITE setEditText NOTIFY editTextChanged)
|
||||
Q_PROPERTY(QString chatBoxReplyId READ chatBoxReplyId WRITE setChatBoxReplyId NOTIFY chatBoxReplyIdChanged)
|
||||
Q_PROPERTY(QString chatBoxEditId READ chatBoxEditId WRITE setChatBoxEditId NOTIFY chatBoxEditIdChanged)
|
||||
Q_PROPERTY(NeoChatUser *chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
|
||||
@@ -93,13 +64,6 @@ class NeoChatRoom : public Quotient::Room
|
||||
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
|
||||
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:
|
||||
enum MessageType {
|
||||
Positive,
|
||||
@@ -152,68 +116,6 @@ public:
|
||||
[[nodiscard]] QString historyVisibility() const;
|
||||
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
|
||||
{
|
||||
return m_hasFileUploading;
|
||||
@@ -254,7 +156,6 @@ public:
|
||||
[[nodiscard]] QString avatarMediaId() const;
|
||||
|
||||
[[nodiscard]] QString eventToString(const Quotient::RoomEvent &evt, Qt::TextFormat format = Qt::PlainText, bool removeReply = true) const;
|
||||
[[nodiscard]] QString eventToGenericString(const Quotient::RoomEvent &evt) const;
|
||||
|
||||
Q_INVOKABLE [[nodiscard]] bool containsUser(const QString &userID) const;
|
||||
Q_INVOKABLE [[nodiscard]] bool isUserBanned(const QString &user) const;
|
||||
@@ -276,9 +177,6 @@ public:
|
||||
QString chatBoxText() const;
|
||||
void setChatBoxText(const QString &text);
|
||||
|
||||
QString editText() const;
|
||||
void setEditText(const QString &text);
|
||||
|
||||
QString chatBoxReplyId() const;
|
||||
void setChatBoxReplyId(const QString &replyId);
|
||||
|
||||
@@ -296,11 +194,6 @@ public:
|
||||
|
||||
QVector<Mention> *mentions();
|
||||
|
||||
/**
|
||||
* @brief Vector of mentions in the current edit text.
|
||||
*/
|
||||
QVector<Mention> *editMentions();
|
||||
|
||||
QString savedText() const;
|
||||
void setSavedText(const QString &savedText);
|
||||
|
||||
@@ -308,16 +201,6 @@ public:
|
||||
|
||||
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
|
||||
Q_INVOKABLE PollHandler *poll(const QString &eventId);
|
||||
#endif
|
||||
@@ -329,8 +212,6 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
int maxRoomVersion() const;
|
||||
|
||||
private:
|
||||
QSet<const Quotient::RoomEvent *> highlights;
|
||||
|
||||
@@ -350,12 +231,10 @@ private:
|
||||
QCoro::Task<void> doUploadFile(QUrl url, QString body = QString());
|
||||
|
||||
QString m_chatBoxText;
|
||||
QString m_editText;
|
||||
QString m_chatBoxReplyId;
|
||||
QString m_chatBoxEditId;
|
||||
QString m_chatBoxAttachmentPath;
|
||||
QVector<Mention> m_mentions;
|
||||
QVector<Mention> m_editMentions;
|
||||
QString m_savedText;
|
||||
#ifdef QUOTIENT_07
|
||||
QCache<QString, PollHandler> m_polls;
|
||||
@@ -378,33 +257,12 @@ Q_SIGNALS:
|
||||
void pushNotificationStateChanged(PushNotificationState::State state);
|
||||
void showMessage(MessageType messageType, const QString &message);
|
||||
void chatBoxTextChanged();
|
||||
void editTextChanged();
|
||||
void chatBoxReplyIdChanged();
|
||||
void chatBoxEditIdChanged();
|
||||
void chatBoxAttachmentPathChanged();
|
||||
void canEncryptRoomChanged();
|
||||
void joinRuleChanged();
|
||||
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:
|
||||
void uploadFile(const QUrl &url, const QString &body = QString());
|
||||
|
||||
@@ -5,18 +5,13 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QImage>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <KNotification>
|
||||
#include <KNotificationReplyAction>
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
#include <accountregistry.h>
|
||||
#else
|
||||
#include "neochataccountregistry.h"
|
||||
#endif
|
||||
|
||||
#include <connection.h>
|
||||
#include <csapi/pushrules.h>
|
||||
#include <jobs/basejob.h>
|
||||
@@ -55,6 +50,10 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
const QString &replyEventId,
|
||||
bool canReply)
|
||||
{
|
||||
if (!NeoChatConfig::self()->showNotifications()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPixmap img;
|
||||
img.convertFromImage(icon);
|
||||
KNotification *notification = new KNotification("message");
|
||||
@@ -70,15 +69,8 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
|
||||
notification->setDefaultAction(i18n("Open NeoChat in this room"));
|
||||
connect(notification, &KNotification::defaultActivated, this, [=]() {
|
||||
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
||||
if (room->localUser()->id() != Controller::instance().activeConnection()->userId()) {
|
||||
#ifdef QUOTIENT_07
|
||||
Controller::instance().setActiveConnection(Accounts.get(room->localUser()->id()));
|
||||
#else
|
||||
Controller::instance().setActiveConnection(AccountRegistry::instance().get(room->localUser()->id()));
|
||||
#endif
|
||||
}
|
||||
RoomManager::instance().enterRoom(room);
|
||||
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
||||
});
|
||||
|
||||
if (canReply) {
|
||||
@@ -99,6 +91,9 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
|
||||
void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QString &title, const QString &sender, const QImage &icon)
|
||||
{
|
||||
if (!NeoChatConfig::self()->showNotifications()) {
|
||||
return;
|
||||
}
|
||||
QPixmap img;
|
||||
img.convertFromImage(icon);
|
||||
KNotification *notification = new KNotification("invite");
|
||||
@@ -108,9 +103,9 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QStri
|
||||
notification->setFlags(KNotification::Persistent);
|
||||
notification->setDefaultAction(i18n("Open this invitation in NeoChat"));
|
||||
connect(notification, &KNotification::defaultActivated, this, [=]() {
|
||||
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
||||
notification->close();
|
||||
RoomManager::instance().enterRoom(room);
|
||||
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
||||
});
|
||||
notification->setActions({i18n("Accept Invitation"), i18n("Reject Invitation")});
|
||||
connect(notification, &KNotification::action1Activated, this, [room, notification]() {
|
||||
@@ -268,10 +263,6 @@ void NotificationsManager::updateNotificationRules(const QString &type)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject accountData = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
|
||||
|
||||
// Update override rules
|
||||
@@ -281,9 +272,7 @@ void NotificationsManager::updateNotificationRules(const QString &type)
|
||||
if (overrideRule["rule_id"] == ".m.rule.master") {
|
||||
bool ruleEnabled = overrideRule["enabled"].toBool();
|
||||
m_globalNotificationsEnabled = !ruleEnabled;
|
||||
if (!m_globalNotificationsSet) {
|
||||
m_globalNotificationsSet = true;
|
||||
}
|
||||
NeoChatConfig::self()->setShowNotifications(m_globalNotificationsEnabled);
|
||||
Q_EMIT globalNotificationsEnabledChanged(m_globalNotificationsEnabled);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ class NotificationsManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool globalNotificationsEnabled MEMBER m_globalNotificationsEnabled WRITE setGlobalNotificationsEnabled NOTIFY globalNotificationsEnabledChanged)
|
||||
Q_PROPERTY(bool globalNotificationsSet MEMBER m_globalNotificationsSet NOTIFY globalNotificationsSetChanged)
|
||||
Q_PROPERTY(PushNotificationAction::Action oneToOneNotificationAction MEMBER m_oneToOneNotificationAction WRITE setOneToOneNotificationAction NOTIFY
|
||||
oneToOneNotificationActionChanged)
|
||||
Q_PROPERTY(PushNotificationAction::Action encryptedOneToOneNotificationAction MEMBER m_encryptedOneToOneNotificationAction WRITE
|
||||
@@ -74,8 +73,7 @@ private:
|
||||
QMultiMap<QString, KNotification *> m_notifications;
|
||||
QHash<QString, QPointer<KNotification>> m_invitations;
|
||||
|
||||
bool m_globalNotificationsEnabled = false;
|
||||
bool m_globalNotificationsSet = false;
|
||||
bool m_globalNotificationsEnabled;
|
||||
PushNotificationAction::Action m_oneToOneNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_encryptedOneToOneNotificationAction = PushNotificationAction::Unknown;
|
||||
PushNotificationAction::Action m_groupChatNotificationAction = PushNotificationAction::Unknown;
|
||||
@@ -109,7 +107,6 @@ private Q_SLOTS:
|
||||
|
||||
Q_SIGNALS:
|
||||
void globalNotificationsEnabledChanged(bool newState);
|
||||
void globalNotificationsSetChanged(bool newState);
|
||||
void oneToOneNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void encryptedOneToOneNotificationActionChanged(PushNotificationAction::Action action);
|
||||
void groupChatNotificationActionChanged(PushNotificationAction::Action action);
|
||||
|
||||
@@ -8,7 +8,6 @@ Name[ca]=NeoChat
|
||||
Name[ca@valencia]=NeoChat
|
||||
Name[cs]=NeoChat
|
||||
Name[de]=NeoChat
|
||||
Name[el]=NeoChat
|
||||
Name[en_GB]=NeoChat
|
||||
Name[es]=NeoChat
|
||||
Name[eu]=NeoChat
|
||||
@@ -29,7 +28,6 @@ Name[pl]=NeoChat
|
||||
Name[pt]=NeoChat
|
||||
Name[pt_BR]=NeoChat
|
||||
Name[ro]=NeoChat
|
||||
Name[ru]=NeoChat
|
||||
Name[sk]=NeoChat
|
||||
Name[sl]=NeoChat
|
||||
Name[sv]=NeoChat
|
||||
@@ -43,8 +41,6 @@ Comment[ar]=اعثر على غرف في نيوتشات
|
||||
Comment[az]=NeoChat-da otaqları tapın
|
||||
Comment[ca]=Cerca sales en el 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[es]=Buscar salas en NeoChat
|
||||
Comment[eu]=Bilatu gelak NeoChat-en
|
||||
@@ -60,7 +56,6 @@ Comment[nl]=Rooms zoeken in NeoChat
|
||||
Comment[pl]=Znajdź pokoje w NeoChat
|
||||
Comment[pt]=Procurar salas no NeoChat
|
||||
Comment[pt_BR]=Encontrar salas no NeoChat
|
||||
Comment[ru]=Поиск комнат NeoChat
|
||||
Comment[sl]=Najdi sobe v NeoChatu
|
||||
Comment[sv]=Sök efter rum i NeoChat
|
||||
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user