Compare commits

..

1 Commits

Author SHA1 Message Date
Joshua Goins
e63cef6363 ReactionModel doesn't need to be registered as a QML type
This isn't used or instiated by QML directly, and seems to be a leftover from long ago. This also stops Qt from complaining on startup.
2024-01-07 14:02:09 -05:00
118 changed files with 12464 additions and 12506 deletions

View File

@@ -2,4 +2,9 @@
; SPDX-License-Identifier: CC0-1.0
[BlueprintSettings]
kde/applications/neochat.packageAppx = True
kde/unreleased/kirigami-addons.version=master
kde/frameworks.version=master
kde/libs.version=master
kde/plasma.version=master
kde/unreleased.version=master
libs/qt.qtMajorVersion=6

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "6.6",
"runtime-version": "6.6-kf6preview",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [

View File

@@ -11,5 +11,3 @@ include:
- /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml
- /gitlab-templates/craft-android-qt6-apks.yml
- /gitlab-templates/craft-appimage-qt6.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml

View File

@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "02")
set(RELEASE_SERVICE_VERSION_MICRO "2")
set(RELEASE_SERVICE_VERSION_MINOR "01")
set(RELEASE_SERVICE_VERSION_MICRO "85")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})

View File

@@ -76,9 +76,3 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test
TEST_NAME reactionmodeltest
)
ecm_add_test(
linkpreviewertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME linkpreviewertest
)

View File

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

View File

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

View File

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

View File

@@ -1,24 +0,0 @@
{
"timeline": {
"events": [
{
"content": {
"body": "https://kde.org",
"format": "org.matrix.custom.html",
"formatted_body": "https://kde.org",
"msgtype": "m.text"
},
"origin_server_ts": 1704648567967,
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 112
},
"event_id": "$validlink:example.org",
"room_id": "!test:example.org"
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -1,35 +0,0 @@
{
"timeline": {
"events": [
{
"content": {
"body": "* ",
"format": "org.matrix.custom.html",
"formatted_body": "no link",
"m.new_content": {
"body": "",
"format": "org.matrix.custom.html",
"formatted_body": "no link",
"msgtype": "m.text"
},
"m.relates_to": {
"event_id": "$validlink:example.org",
"rel_type": "m.replace"
},
"msgtype": "m.text",
"type": "m.room.message"
},
"origin_server_ts": 1704648614969,
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 65
},
"event_id": "$nolink:example.org",
"room_id": "!test:example.org"
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,11 +61,12 @@ private Q_SLOTS:
void genericBody_data();
void genericBody();
void nullGenericBody();
void markdownBody();
void subtitle();
void nullSubtitle();
void mediaInfo();
void nullMediaInfo();
void linkPreviewer();
void nullLinkPreviewer();
void hasReply();
void nullHasReply();
void replyId();
@@ -347,13 +348,6 @@ void EventHandlerTest::nullGenericBody()
QCOMPARE(noEventHandler.getGenericBody(), QString());
}
void EventHandlerTest::markdownBody()
{
eventHandler.setEvent(room->messageEvents().at(0).get());
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
}
void EventHandlerTest::subtitle()
{
auto event = room->messageEvents().at(0).get();
@@ -405,6 +399,28 @@ void EventHandlerTest::nullMediaInfo()
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
}
void EventHandlerTest::linkPreviewer()
{
auto event = room->messageEvents().at(2).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.getLinkPreviewer()->url(), QUrl("https://kde.org"_ls));
event = room->messageEvents().at(0).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::nullLinkPreviewer()
{
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getLinkPreviewer(), nullptr);
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::hasReply()
{
auto event = room->messageEvents().at(5).get();

View File

@@ -1,104 +0,0 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject>
#include <QTest>
#include "linkpreviewer.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "utils.h"
#include "testutils.h"
using namespace Quotient;
class LinkPreviewerTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
void editedLink();
};
void LinkPreviewerTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:example.org"));
room = new TestUtils::TestRoom(connection, QStringLiteral("!test:example.org"));
}
void LinkPreviewerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("eventSource");
QTest::addColumn<QUrl>("testOutputLink");
QTest::newRow("plainHttps") << QStringLiteral("test-validplainlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttps") << QStringLiteral("test-validrichlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("plainWww") << QStringLiteral("test-validplainwwwlink-event.json") << QUrl("www.example.org"_ls);
QTest::newRow("multipleHttps") << QStringLiteral("test-multiplelink-event.json") << QUrl("www.example.org"_ls);
}
void LinkPreviewerTest::linkPreviewsMatch()
{
QFETCH(QString, eventSource);
QFETCH(QUrl, testOutputLink);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), testOutputLink);
}
void LinkPreviewerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("eventSource");
QTest::newRow("mxc") << QStringLiteral("test-invalidmxclink-event.json");
QTest::newRow("matrixTo") << QStringLiteral("test-invalidmatrixtolink-event.json");
QTest::newRow("noSpace") << QStringLiteral("test-invalidnospacelink-event.json");
}
void LinkPreviewerTest::linkPreviewsReject()
{
QFETCH(QString, eventSource);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(linkPreviewer.empty(), true);
QCOMPARE(linkPreviewer.url(), QUrl());
}
void LinkPreviewerTest::editedLink()
{
room->syncNewEvents(QStringLiteral("test-linkpreviewerintial-sync.json"));
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto linkPreviewer = LinkPreviewer(room, event);
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), QUrl("https://kde.org"_ls));
room->syncNewEvents(QStringLiteral("test-linkpreviewerreplace-sync.json"));
QCOMPARE(linkPreviewer.empty(), true);
QCOMPARE(linkPreviewer.url(), QUrl());
}
QTEST_MAIN(LinkPreviewerTest)
#include "linkpreviewertest.moc"

View File

@@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <Quotient/events/event.h>
#include <Quotient/syncdata.h>
#include "neochatroom.h"
@@ -39,17 +38,4 @@ public:
}
}
};
template<Quotient::EventClass EventT>
inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFileName)
{
if (!eventFileName.isEmpty()) {
QFile testEventFile;
testEventFile.setFileName(QLatin1String(DATA_DIR) + u'/' + eventFileName);
testEventFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
return Quotient::loadEvent<EventT>(testSyncJson);
}
return nullptr;
}
}

View File

@@ -64,6 +64,11 @@ private Q_SLOTS:
void receiveRichEdited();
void receiveLineSeparator();
void receiveRichCodeUrl();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
};
void TextHandlerTest::initTestCase()
@@ -518,6 +523,53 @@ void TextHandlerTest::receiveLineSeparator()
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
}
void TextHandlerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org"_ls)});
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org"_ls)});
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org"_ls)});
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
<< QList<QUrl>({
QUrl("https://kde.org"_ls),
QUrl("www.example.org"_ls),
});
}
void TextHandlerTest::linkPreviewsMatch()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF") << QList<QUrl>();
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org") << QList<QUrl>();
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org") << QList<QUrl>();
}
void TextHandlerTest::linkPreviewsReject()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::receiveRichCodeUrl()
{
auto input = QStringLiteral("<code>https://kde.org</code>");

View File

@@ -59,8 +59,7 @@
<summary xml:lang="fi">Keskustelu ystäviesi kanssa Matrixissa</summary>
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
<summary xml:lang="hu">Csevegjen barátaival a matrixon</summary>
<summary xml:lang="ia">Starta Conversation con tu amicos sur matrix</summary>
<summary xml:lang="ia">Starta Conversation conntu amicos sur matrix</summary>
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
<summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
@@ -70,7 +69,7 @@
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
<summary xml:lang="sv">Chatta med dina vänner på Matrix</summary>
<summary xml:lang="ta">மேட்ரிக்ஸு மூலம் உங்கள் நண்பர்களிடம் பேசலாம்</summary>
<summary xml:lang="tr">Matrixte arkadaşlarınızla sohbet edin</summary>
<summary xml:lang="tr">Matrix'te arkadaşlarınızla sohbet edin</summary>
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
<summary xml:lang="zh-CN">在 Matrix 上与朋友聊天</summary>
@@ -88,7 +87,6 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="fi">NeoChat on asiakassovellus Matrixille, hajautetulle pikaviestinyhteyskäytännölle. Sillä voi lähettää teksti-, video- ja ääniviestejä perheelle, tutuille ja ystäville. Se käyttää KDE-kehystä ja erityisesti Kirigamia tuottaakseen mukautuvan monialustaisen käyttökokemuksen.</p>
<p xml:lang="fr">NeoChat est un client pour le protocole Matrix, un protocole décentralisé de communications pour messagerie instantané. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis. Il utilise les environnements de développement et plus précisément Kirigami pour fournir une expérience convergente sur plusieurs plate-formes. </p>
<p xml:lang="gl">NeoChat é un cliente para Matrix, o protocolo de comunicación descentralizada para mensaxaría instantánea. Podes enviar mensaxes de texto, vídeos e ficheiros de son á túa familia, colegas e amizades. Usas infraestruturas de KDE e principalmente Kirigami para proporcionar unha experiencia de uso converxente para varias plataformas.</p>
<p xml:lang="hu">A NeoChat egy kliens a Matrixhoz, az azonnali üzenetküldés decentralizált komunikációs protokolljához. Szöveges üzeneteket, videókat és hangfájlokat küldhet családjának, kollégáinak és barátainak. A KDE keretrendszert használja, a Kirigaminak köszönhetően konvergens élményt nyújt több platformon is.</p>
<p xml:lang="ia">NeoChat es un cliente per Matrix, le protocollo de communication decentralisate per messager instantanee. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante. Illo usa KDE frameworks e super toto Kirigamii forni un experientia convergente trans platteforme multiple.</p>
<p xml:lang="it">NeoChat è un client per Matrix, il protocollo di comunicazione decentralizzato per la messaggistica istantanea. Ti consente di inviare messaggi di testo, video e file audio a familiari, colleghi e amici. Utilizza i framework KDE e in particolare Kirigami per fornire un'esperienza convergente su più piattaforme.</p>
<p xml:lang="ka">NeoChat არის Matrix კლიენტი. ის საშუალებას გაძლევთ გაგზავნოთ ტექსტური შეტყობინებები, ვიდეოები და აუდიო ფაილები თქვენს ოჯახს, კოლეგებსა და მეგობრებს მატრიქსის პროტოკოლის გამოყენებით.</p>
@@ -114,7 +112,6 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p>
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p>
<p xml:lang="ia">NeoChat aspira a esser un application plenemente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
<p xml:lang="ka">NeoChat-ი მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარგდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
@@ -125,7 +122,7 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
<p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifrelemenin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifreleme'nin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.xx</p>
<p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p>
@@ -140,7 +137,6 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="fi">Matrix-määritelmän kehittyessä NeoChat tukee myös monia epävakaita ominaisuuksia. Tällä hetkellä näitä ovat:</p>
<p xml:lang="fr">En raison de la nature du développement des spécifications du protocole Matrix, NeoChat prend également en charge de nombreuses fonctionnalités instables. Actuellement, ce sont :</p>
<p xml:lang="gl">Debido á natureza do desenvolvemento da especificación de Matrix, NeoChat tamén inclúe varias funcionalidades non estábeis:</p>
<p xml:lang="hu">A Matrix specifikáció fejlesztésének jellegéből adódóan a NeoChat számos instabil funkciót is támogat. Jelenleg a következőket:</p>
<p xml:lang="ia">Debite al natura del disveloppamento de specification de Matrix NeoChat tamben supporta numerose characteristicas instabile. Currentemente istes es:</p>
<p xml:lang="it">A causa della natura dello sviluppo delle specifiche Matrix, NeoChat supporta anche numerose funzionalità instabili. Attualmente queste sono:</p>
<p xml:lang="ka">Matrix-ის სპეციფიკაციის განვითარების ბუნების გამო NeoChat-ს ასევე აქვს უამრავი არასტაბილური ფუნქციაც. ახლა ისინია:</p>
@@ -168,7 +164,6 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="fi">Kyselyt MSC3381</li>
<li xml:lang="fr">Sondages - MSC3381</li>
<li xml:lang="gl">Enquisas — MSC3381</li>
<li xml:lang="hu">Szavazások - MSC3381</li>
<li xml:lang="ia">Inquestas - MSC3381</li>
<li xml:lang="it">Sondaggi - MSC3381</li>
<li xml:lang="ka">Polls - MSC3381</li>
@@ -180,7 +175,7 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="sl">Polls - MSC3381</li>
<li xml:lang="sv">Polls - MSC3381</li>
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
<li xml:lang="tr">Anketler MSC3381</li>
<li xml:lang="tr">Anketler - MSC3381</li>
<li xml:lang="uk">Опитування - MSC3381</li>
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
<li xml:lang="zh-TW">投票 - MSC3381</li>
@@ -195,7 +190,6 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="fi">Tarrapakkaukset MSC2545</li>
<li xml:lang="fr">Paquets d'auto-collants - MSC2545</li>
<li xml:lang="gl">Paquetes de adhesivos — MSC2545</li>
<li xml:lang="hu">Matricacsomagok - MSC2545</li>
<li xml:lang="ia">Etiquetta gummate (sticker) -MSC2545</li>
<li xml:lang="it">Pacchetti di adesivi - MSC2545</li>
<li xml:lang="ka">სტიკერების პაკეტები - MSC2545</li>
@@ -207,7 +201,7 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="sl">Sticker Packs - MSC2545</li>
<li xml:lang="sv">Sticker Packs - MSC2545</li>
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
<li xml:lang="tr">Yapışkan Paketleri MSC2545</li>
<li xml:lang="tr">Yapışkan Paketleri - MSC2545</li>
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
<li xml:lang="zh-TW">貼圖包 - MSC2545</li>
@@ -222,7 +216,6 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="fi">Sijaintitapahtumat MSC3488</li>
<li xml:lang="fr">Événements de lieu - MSC3488</li>
<li xml:lang="gl">Localización de eventos — MSC3488</li>
<li xml:lang="hu">Események helyadatai - MSC3488</li>
<li xml:lang="ia">Eventos de Location - MSC3488</li>
<li xml:lang="it">Località eventi - MSC3488</li>
<li xml:lang="ka">მდებარეობის მოვლენები - MSC3488</li>
@@ -234,7 +227,7 @@ to provide a convergent experience across multiple platforms.</p>
<li xml:lang="sl">Location Events - MSC3488</li>
<li xml:lang="sv">Location Events - MSC3488</li>
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
<li xml:lang="tr">Konum Etkinlikleri MSC3488</li>
<li xml:lang="tr">Konum Etkinlikleri - MSC3488</li>
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
<li xml:lang="zh-TW">位置事件 - MSC3488</li>
@@ -314,7 +307,6 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</caption>
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
<caption xml:lang="hu">A fő nézet a szobalistával, csevegéssel és szobainformációkkal</caption>
<caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption>
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
<caption xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</caption>
@@ -344,7 +336,6 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="fi">Kirjautumisnäkymä</caption>
<caption xml:lang="fr">Écran de connexion</caption>
<caption xml:lang="gl">Pantalla de identificación.</caption>
<caption xml:lang="hu">Bejelentkező képernyő</caption>
<caption xml:lang="ia">Schermo de accesso</caption>
<caption xml:lang="it">Schermata di accesso</caption>
<caption xml:lang="ka">შესვლის ეკრანი</caption>
@@ -366,26 +357,6 @@ to provide a convergent experience across multiple platforms.</p>
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="24.02.2" date="2024-04-11"/>
<release version="24.02.1" date="2024-03-21"/>
<release version="24.02.0" date="2024-02-28">
<url>https://kde.org/announcements/megarelease/6/#neochat</url>
<description>
<p>In the newest version, when launching the app, you will get a welcome page that lets you choose which account you want to use and lets you log in to other accounts. The welcome screen will also warn you when NeoChat cannot load an account.</p>
<p>NeoChat will also let you register a new account directly from the app itself. Deactivating your Matrix account is also possible from within NeoChat.</p>
<p>Spaces are a relatively new feature of Matrix that let you group chat channels together. This is used to improve the discoverability of rooms, manage large communities, or just tidy all the channels you are in. You can now do all this without leaving NeoChat.</p>
<p>NeoChat won't let you miss any new notifications anymore. We added a new page that includes all your recent notifications and when NeoChat is closed, you will still be able to receive push notifications. The main timeline will let you know when more messages are loading and when you reach the end of it.</p>
<p>More NeoChat Goodies</p>
<ul>
<li>QR Codes to share contacts</li>
<li>Improved room upgrades</li>
<li>Added button to reject invitation and ignore user</li>
<li>Display device security details</li>
<li>Added room security settings</li>
</ul>
</description>
</release>
<release version="23.08.5" date="2024-02-15"/>
<release version="23.08.4" date="2023-12-07"/>
<release version="23.08.3" date="2023-11-09"/>
<release version="23.08.2" date="2023-10-12"/>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -365,10 +365,6 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11)
target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp)
endif()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
@@ -505,7 +501,7 @@ if(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF6::DBusAddons AND NOT WIN32)
if(TARGET KF6::DBusAddons)
target_link_libraries(neochat PUBLIC KF6::DBusAddons)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()

View File

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

View File

@@ -3,7 +3,6 @@
#include "chatbarcache.h"
#include "chatdocumenthandler.h"
#include "eventhandler.h"
#include "neochatroom.h"
@@ -119,7 +118,7 @@ QString ChatBarCache::relationMessage() const
eventhandler.setRoom(room);
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
eventhandler.setEvent(&**event);
return eventhandler.getMarkdownBody();
return eventhandler.getPlainBody();
}
return {};
}
@@ -165,54 +164,6 @@ QList<Mention> *ChatBarCache::mentions()
return &m_mentions;
}
void ChatBarCache::updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler)
{
documentHandler->setDocument(document);
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return;
}
if (m_relationId.isEmpty()) {
return;
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return;
}
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
if (const auto &roomMessageEvent = &*event->viewAs<Quotient::RoomMessageEvent>()) {
// Replaces the mentions that are baked into the HTML but plaintext in the original markdown
const QRegularExpression re(QStringLiteral(R"lit(<a\shref="https:\/\/matrix.to\/#\/([\S]*)"\s?>([\S]*)<\/a>)lit"));
m_mentions.clear();
int linkSize = 0;
auto matches = re.globalMatch(EventHandler::rawMessageBody(*roomMessageEvent));
while (matches.hasNext()) {
const QRegularExpressionMatch match = matches.next();
if (match.hasMatch()) {
const QString id = match.captured(1);
const QString name = match.captured(2);
const int position = match.capturedStart(0) - linkSize;
const int end = position + name.length();
linkSize += match.capturedLength(0) - name.length();
QTextCursor cursor(documentHandler->document()->textDocument());
cursor.setPosition(position);
cursor.setPosition(end, QTextCursor::KeepAnchor);
cursor.setKeepPositionOnInsert(true);
m_mentions.push_back(Mention{.cursor = cursor, .text = name, .start = position, .position = end, .id = id});
}
}
}
}
}
QString ChatBarCache::savedText() const
{
return m_savedText;

View File

@@ -5,11 +5,8 @@
#include <QObject>
#include <QQmlEngine>
#include <QQuickTextDocument>
#include <QTextCursor>
class ChatDocumentHandler;
/**
* @brief Defines a user mention in the current chat or edit text.
*/
@@ -177,11 +174,6 @@ public:
*/
QList<Mention> *mentions();
/**
* @brief Update the mentions in @p document when editing a message.
*/
Q_INVOKABLE void updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler);
/**
* @brief Get the saved chat bar text.
*/

View File

@@ -7,7 +7,10 @@
#include <qt6keychain/keychain.h>
#include <KLocalizedString>
#include <KWindowConfig>
#include <QFile>
#include <QFileInfo>
#include <QGuiApplication>
#include <QNetworkProxy>
#include <QQuickTextDocument>
@@ -25,11 +28,13 @@
#include <Quotient/eventstats.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/user.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "roommanager.h"
#include "windowcontroller.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
@@ -72,7 +77,8 @@ Controller::Controller(QObject *parent)
});
#ifndef Q_OS_WINDOWS
const auto unixExitHandler = [](int) -> void {
// Setup Unix signal handlers
const auto unixExitHandler = [](int /*sig*/) -> void {
QCoreApplication::quit();
};
@@ -98,7 +104,7 @@ Controller::Controller(QObject *parent)
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
if (m_accountRegistry.size() > oldAccountCount) {
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
connect(connection, &NeoChatConnection::syncDone, this, [connection]() {
connect(connection, &NeoChatConnection::syncDone, this, [this, connection]() {
NotificationsManager::instance().handleNotifications(connection);
});
connectSingleShot(connection, &NeoChatConnection::syncDone, this, [this, connection] {
@@ -138,7 +144,7 @@ void Controller::addConnection(NeoChatConnection *c)
c->setLazyLoading(true);
connect(c, &NeoChatConnection::syncDone, this, [c] {
connect(c, &NeoChatConnection::syncDone, this, [this, c] {
c->sync(30000);
c->saveState();
});
@@ -146,6 +152,12 @@ void Controller::addConnection(NeoChatConnection *c)
dropConnection(c);
});
connect(c, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
if (job->error() == BaseJob::UserConsentRequired) {
Q_EMIT userConsentRequired(job->errorUrl());
}
});
c->sync();
Q_EMIT connectionAdded(c);
@@ -162,13 +174,18 @@ void Controller::dropConnection(NeoChatConnection *c)
void Controller::invokeLogin()
{
const auto accounts = SettingsGroup("Accounts"_ls).childGroups();
QString id = NeoChatConfig::self()->activeConnection();
for (const auto &accountId : accounts) {
AccountSettings account{accountId};
m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged();
if (id.isEmpty()) {
// handle case where the account config is empty
id = accountId;
}
if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, id, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId};
QString accessToken;
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
@@ -178,13 +195,28 @@ void Controller::invokeLogin()
}
auto connection = new NeoChatConnection(account.homeserver());
m_connectionsLoading[accountId] = connection;
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
connect(connection, &NeoChatConnection::connected, this, [this, connection, id] {
connection->loadState();
addConnection(connection);
m_accountsLoading.removeAll(connection->userId());
m_connectionsLoading.remove(accountId);
Q_EMIT accountsLoadingChanged();
if (connection->userId() == id) {
setActiveConnection(connection);
}
});
connect(connection, &NeoChatConnection::loginError, this, [this, connection](const QString &error, const QString &) {
if (error == "Unrecognised access token"_ls) {
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"), {});
connection->logout(false);
} else if (error == "Connection closed"_ls) {
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), {});
connection->logout(true);
}
});
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
Q_EMIT errorOccured(i18n("Network Error: %1", error), {});
@@ -293,16 +325,24 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
}
m_connection = connection;
if (connection != nullptr) {
NeoChatConfig::self()->setActiveConnection(connection->userId());
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
}
});
} else {
NeoChatConfig::self()->setActiveConnection(QString());
}
NeoChatConfig::self()->save();
Q_EMIT activeConnectionChanged();
}
void Controller::saveWindowGeometry()
{
WindowController::instance().saveGeometry();
}
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
{
// HACK: Workaround bug QTBUG 93281
@@ -380,13 +420,3 @@ void Controller::setTestMode(bool test)
{
testMode = test;
}
void Controller::removeConnection(const QString &userId)
{
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
auto connection = m_connectionsLoading[userId];
m_accountsLoading.removeAll(userId);
Q_EMIT accountsLoadingChanged();
SettingsGroup("Accounts"_ls).remove(userId);
}
}

View File

@@ -110,8 +110,6 @@ public:
static void setTestMode(bool testMode);
Q_INVOKABLE void removeConnection(const QString &userId);
private:
explicit Controller(QObject *parent = nullptr);
@@ -125,7 +123,6 @@ private:
Quotient::AccountRegistry m_accountRegistry;
QStringList m_accountsLoading;
QMap<QString, QPointer<Quotient::Connection>> m_connectionsLoading;
QString m_endpoint;
private Q_SLOTS:
@@ -137,5 +134,9 @@ Q_SIGNALS:
void connectionAdded(NeoChatConnection *connection);
void connectionDropped(NeoChatConnection *connection);
void activeConnectionChanged();
void userConsentRequired(QUrl url);
void accountsLoadingChanged();
public Q_SLOTS:
void saveWindowGeometry();
};

View File

@@ -300,27 +300,6 @@ bool EventHandler::isHidden()
return false;
}
QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
{
if (event.hasFileContent()) {
auto fileCaption = event.content()->fileInfo()->originalName;
if (fileCaption.isEmpty()) {
fileCaption = event.plainBody();
} else if (event.content()->fileInfo()->originalName != event.plainBody()) {
fileCaption = event.plainBody() + " | "_ls + fileCaption;
}
return fileCaption;
}
QString body;
if (event.hasTextContent() && event.content()) {
body = static_cast<const MessageEventContent::TextContent *>(event.content())->body;
} else {
body = event.plainBody();
}
return body;
}
QString EventHandler::getRichBody(bool stripNewlines) const
{
if (m_event == nullptr) {
@@ -339,22 +318,6 @@ QString EventHandler::getPlainBody(bool stripNewlines) const
return getBody(m_event, Qt::PlainText, stripNewlines);
}
QString EventHandler::getMarkdownBody() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "getMarkdownBody called with m_event set to nullptr.";
return {};
}
if (!m_event->is<RoomMessageEvent>()) {
qCWarning(EventHandling) << "getMarkdownBody called when m_event isn't a RoomMessageEvent.";
return {};
}
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
return roomMessageEvent->plainBody();
}
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
{
if (event->isRedacted()) {
@@ -392,7 +355,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (e.repeatsState()) {
auto text = i18n("reinvited %1 to the room", subjectName);
if (!e.reason().isEmpty()) {
text += i18nc("Optional reason for an invitation", ": %1") + (prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
text += i18nc("Optional reason for an invitation", ": %1") + e.reason().toHtmlEscaped();
}
return text;
}
@@ -416,9 +379,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (!e.newDisplayName()) {
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 to %1",
prettyPrint ? e.newDisplayName()->toHtmlEscaped() : *e.newDisplayName());
text = i18nc("their refers to a singular user", "changed their display name to %1", e.newDisplayName()->toHtmlEscaped());
}
}
if (e.isAvatarUpdate()) {
@@ -454,7 +415,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (e.reason().isEmpty()) {
return i18n("banned %1 from the room", subjectName);
} else {
return i18n("banned %1 from the room: %2", subjectName, prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
return i18n("banned %1 from the room: %2", subjectName, e.reason().toHtmlEscaped());
}
} else {
return i18n("self-banned from the room");
@@ -470,8 +431,8 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
[](const RoomCanonicalAliasEvent &e) {
return (e.alias().isEmpty()) ? i18n("cleared the room main alias") : i18n("set the room main alias to: %1", e.alias());
},
[prettyPrint](const RoomNameEvent &e) {
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", prettyPrint ? e.name().toHtmlEscaped() : e.name());
[](const RoomNameEvent &e) {
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", e.name().toHtmlEscaped());
},
[prettyPrint, stripNewlines](const RoomTopicEvent &e) {
return (e.topic().isEmpty()) ? i18n("cleared the topic")
@@ -486,15 +447,14 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
[](const EncryptionEvent &) {
return i18n("activated End-to-End Encryption");
},
[prettyPrint](const RoomCreateEvent &e) {
return e.isUpgrade()
? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()))
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()));
[](const RoomCreateEvent &e) {
return e.isUpgrade() ? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped())
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped());
},
[](const RoomPowerLevelsEvent &) {
return i18nc("'power level' means permission level", "changed the power levels for this room");
},
[prettyPrint](const StateEvent &e) {
[](const StateEvent &e) {
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
return i18n("changed the server access control lists for this room");
}
@@ -511,7 +471,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
return e.contentJson()["description"_ls].toString();
}
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
},
[](const PollStartEvent &e) {
return e.question();
@@ -814,6 +774,43 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
return mediaInfo;
}
QSharedPointer<LinkPreviewer> EventHandler::getLinkPreviewer() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_room set to nullptr.";
return nullptr;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_event set to nullptr.";
return nullptr;
}
if (!m_event->is<RoomMessageEvent>()) {
return nullptr;
}
QString text;
auto event = eventCast<const RoomMessageEvent>(m_event);
if (event->hasTextContent()) {
auto textContent = static_cast<const EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
TextHandler textHandler;
textHandler.setData(text);
QList<QUrl> links = textHandler.getLinkPreviews();
if (links.size() > 0) {
return QSharedPointer<LinkPreviewer>(new LinkPreviewer(nullptr, m_room, links.size() > 0 ? links[0] : QUrl()));
} else {
return nullptr;
}
}
bool EventHandler::hasReply() const
{
if (m_event == nullptr) {

View File

@@ -159,14 +159,6 @@ public:
*/
bool isHidden();
/**
* @brief Output a string for the room message content without any formatting.
*
* This is the content of the formatted_body key if present or the body key if
* not.
*/
static QString rawMessageBody(const Quotient::RoomMessageEvent &event);
/**
* @brief Output a string for the message content ready for display in a rich text field.
*
@@ -199,13 +191,6 @@ public:
*/
QString getPlainBody(bool stripNewlines = false) const;
/**
* @brief Output the original body for the message content, useful for editing the original message.
*
* The event type must be a room message event.
*/
QString getMarkdownBody() const;
/**
* @brief Output a generic string for the message content ready for display.
*
@@ -246,6 +231,16 @@ public:
*/
QVariantMap getMediaInfo() const;
/**
* @brief Return a LinkPreviewer object for the event.
*
* A nullptr will be returned for any event that doesn't have any links so the
* return should be null checked and an empty LinkPreviewer provided if null.
*
* @sa LinkPreviewer
*/
QSharedPointer<LinkPreviewer> getLinkPreviewer() const;
/**
* @brief Whether the event is a reply to another in the timeline.
*/

View File

@@ -1,36 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "fakerunner.h"
#include <QCoreApplication>
#include <QDBusMetaType>
Q_SCRIPTABLE RemoteActions FakeRunner::Actions()
{
QCoreApplication::quit();
return {};
}
Q_SCRIPTABLE RemoteMatches FakeRunner::Match(const QString &searchTerm)
{
QCoreApplication::quit();
return {};
}
Q_SCRIPTABLE void FakeRunner::Run(const QString &id, const QString &actionId)
{
QCoreApplication::quit();
}
FakeRunner::FakeRunner()
: QObject()
{
qDBusRegisterMetaType<RemoteMatch>();
qDBusRegisterMetaType<RemoteMatches>();
qDBusRegisterMetaType<RemoteAction>();
qDBusRegisterMetaType<RemoteActions>();
qDBusRegisterMetaType<RemoteImage>();
}
#include "moc_fakerunner.cpp"

View File

@@ -1,31 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QDBusContext>
#include "runner.h"
/**
* This is a close-to-identical copy of the regular Runner interface,
* only used when activated for push notifications. This stubs it out so
* Plasma Search and Kickoff doesn't accidentally activate the push notification
* service.
*
* @sa Runner
*/
class FakeRunner : public QObject, protected QDBusContext
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.krunner1")
public:
Q_SCRIPTABLE RemoteActions Actions();
Q_SCRIPTABLE RemoteMatches Match(const QString &searchTerm);
Q_SCRIPTABLE void Run(const QString &id, const QString &actionId);
FakeRunner();
};

View File

@@ -3,37 +3,25 @@
#include "linkpreviewer.h"
#include "controller.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/content-repo.h>
#include <Quotient/events/roommessageevent.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "utils.h"
using namespace Quotient;
LinkPreviewer::LinkPreviewer(const NeoChatRoom *room, const Quotient::RoomMessageEvent *event)
: QObject(nullptr)
LinkPreviewer::LinkPreviewer(QObject *parent, const NeoChatRoom *room, const QUrl &url)
: QObject(parent)
, m_currentRoom(room)
, m_event(event)
, m_loaded(false)
, m_url(linkPreview(event))
, m_url(url)
{
connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
if (m_event != nullptr && m_currentRoom != nullptr) {
loadUrlPreview();
loadUrlPreview();
if (m_currentRoom) {
connect(m_currentRoom, &NeoChatRoom::urlPreviewEnabledChanged, this, &LinkPreviewer::loadUrlPreview);
// Make sure that we react to edits
connect(m_currentRoom, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
if (m_event->id() == newEvent->id()) {
m_event = eventCast<const Quotient::RoomMessageEvent>(newEvent);
m_url = linkPreview(m_event);
Q_EMIT urlChanged();
loadUrlPreview();
}
});
}
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
}
@@ -63,6 +51,15 @@ QUrl LinkPreviewer::url() const
return m_url;
}
void LinkPreviewer::setUrl(QUrl url)
{
if (url != m_url) {
m_url = url;
urlChanged();
loadUrlPreview();
}
}
void LinkPreviewer::loadUrlPreview()
{
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
@@ -101,38 +98,4 @@ bool LinkPreviewer::empty() const
return m_url.isEmpty();
}
QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
{
if (event == nullptr) {
return {};
}
QString text;
if (event->hasTextContent()) {
auto textContent = static_cast<const Quotient::EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
auto data = text.remove(TextRegex::removeRichReply);
auto linksMatch = TextRegex::url.globalMatch(data);
while (linksMatch.hasNext()) {
auto link = linksMatch.next().captured();
if (!link.contains(QStringLiteral("matrix.to"))) {
return QUrl(link);
}
}
return {};
}
bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
{
return !linkPreview(event).isEmpty();
}
#include "moc_linkpreviewer.cpp"

View File

@@ -7,11 +7,6 @@
#include <QQmlEngine>
#include <QUrl>
namespace Quotient
{
class RoomMessageEvent;
}
class NeoChatRoom;
/**
@@ -30,7 +25,7 @@ class LinkPreviewer : public QObject
/**
* @brief The URL to get the preview for.
*/
Q_PROPERTY(QUrl url READ url NOTIFY urlChanged)
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
/**
* @brief Whether the preview information has been loaded.
@@ -60,25 +55,18 @@ class LinkPreviewer : public QObject
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
public:
explicit LinkPreviewer(const NeoChatRoom *room = nullptr, const Quotient::RoomMessageEvent *event = nullptr);
explicit LinkPreviewer(QObject *parent = nullptr, const NeoChatRoom *room = nullptr, const QUrl &url = {});
[[nodiscard]] QUrl url() const;
void setUrl(QUrl);
[[nodiscard]] bool loaded() const;
[[nodiscard]] QString title() const;
[[nodiscard]] QString description() const;
[[nodiscard]] QUrl imageSource() const;
[[nodiscard]] bool empty() const;
/**
* @brief Whether the given event has at least 1 pre-viewable link.
*
* A link is only pre-viewable if it is http, https or something starting with www.
*/
static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
private:
const NeoChatRoom *m_currentRoom;
const Quotient::RoomMessageEvent *m_event;
const NeoChatRoom *m_currentRoom = nullptr;
bool m_loaded;
QString m_title = QString();
@@ -88,14 +76,6 @@ private:
void loadUrlPreview();
/**
* @brief Return the link to be previewed from the given event.
*
* This function is designed to give only links that should be previewed so
* http, https or something starting with www. The first valid link is returned.
*/
static QUrl linkPreview(const Quotient::RoomMessageEvent *event);
Q_SIGNALS:
void loadedChanged();
void titleChanged();

View File

@@ -48,11 +48,6 @@
#ifdef HAVE_RUNNER
#include "runner.h"
#include <QDBusConnection>
#include <QDBusMetaType>
#endif
#if defined(HAVE_RUNNER) && defined(HAVE_KUNIFIEDPUSH)
#include "fakerunner.h"
#endif
#ifdef Q_OS_WINDOWS
@@ -135,7 +130,7 @@ int main(int argc, char *argv[])
QStringLiteral(NEOCHAT_VERSION_STRING),
i18n("Matrix client"),
KAboutLicense::GPL_V3,
i18n("© 2018-2020 Black Hat, 2020-2024 KDE Community"));
i18n("© 2018-2020 Black Hat, 2020-2023 KDE Community"));
about.addAuthor(i18n("Carl Schwan"),
i18n("Maintainer"),
QStringLiteral("carl@carlschwan.eu"),
@@ -201,14 +196,6 @@ int main(int argc, char *argv[])
// We want to be replaceable by the main client
KDBusService service(KDBusService::Replace);
#ifdef HAVE_RUNNER
// If we are built with KRunner and KUnifiedPush support, we need to do something special.
// Because KRunner may call us on the D-Bus (under the same service name org.kde.neochat) then it may
// accidentally activate us for push notifications instead. If this happens, then immediately quit if the fake
// runner is called.
QDBusConnection::sessionBus().registerObject("/RoomRunner"_ls, new FakeRunner(), QDBusConnection::ExportScriptableContents);
#endif
Controller::listenForNotifications();
return QCoreApplication::exec();
}

View File

@@ -12,6 +12,7 @@
#include <KLocalizedString>
#include "controller.h"
#include "neochatconnection.h"
#include <Quotient/connection.h>

View File

@@ -4,6 +4,7 @@
#include "actionsmodel.h"
#include "chatbarcache.h"
#include "controller.h"
#include "neochatroom.h"
#include "roommanager.h"
#include <Quotient/events/roommemberevent.h>
@@ -354,12 +355,17 @@ QList<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
return QString();
}
if (room->connection()->ignoredUsers().contains(text)) {
auto user = room->connection()->users()[text];
if (room->connection()->ignoredUsers().contains(user->id())) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
return QString();
}
room->connection()->addToIgnoredUsers(room->connection()->user(text));
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
if (user) {
room->connection()->addToIgnoredUsers(user);
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
} else {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not a known user", "%1 is not a known user.", text));
}
return QString();
},
false,
@@ -377,12 +383,17 @@ QList<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
return QString();
}
if (!room->connection()->ignoredUsers().contains(text)) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
return QString();
auto user = room->connection()->users()[text];
if (user) {
if (!room->connection()->ignoredUsers().contains(user->id())) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
return QString();
}
room->connection()->removeFromIgnoredUsers(user);
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
} else {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not a known user", "%1 is not a known user.", text));
}
room->connection()->removeFromIgnoredUsers(room->connection()->user(text));
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
return QString();
},
false,

View File

@@ -6,8 +6,10 @@
#include <QImage>
#include <QMimeDatabase>
#include "controller.h"
#include "emojimodel.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/account-data.h>
#include <Quotient/csapi/content-repo.h>

View File

@@ -6,8 +6,9 @@
#include <QAbstractListModel>
#include <QQmlEngine>
#include <QRegularExpression>
#include <memory>
#include "neochatconnection.h"
class NeoChatConnection;
struct CustomEmoji {
QString name; // with :semicolons:

View File

@@ -3,6 +3,7 @@
#include "devicesmodel.h"
#include "controller.h"
#include "jobs/neochatdeletedevicejob.h"
#include <QDateTime>

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "messageeventmodel.h"
#include "linkpreviewer.h"
#include "messageeventmodel_logging.h"
#include "neochatconfig.h"
@@ -23,7 +22,6 @@
#include "eventhandler.h"
#include "events/pollevent.h"
#include "models/reactionmodel.h"
#include "texthandler.h"
using namespace Quotient;
@@ -239,10 +237,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
});
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
const RoomMessageEvent *message = eventCast<const RoomMessageEvent>(newEvent);
if (message != nullptr) {
createEventObjects(message);
}
});
connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) {
if (eventId.isEmpty()) { // How did we get here?
@@ -726,14 +720,14 @@ void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *eve
{
auto eventId = event->id();
if (m_linkPreviewers.contains(eventId)) {
if (!LinkPreviewer::hasPreviewableLinks(event)) {
m_linkPreviewers.remove(eventId);
}
EventHandler eventHandler;
eventHandler.setRoom(m_currentRoom);
eventHandler.setEvent(event);
if (auto linkPreviewer = eventHandler.getLinkPreviewer()) {
m_linkPreviewers[eventId] = linkPreviewer;
} else {
if (LinkPreviewer::hasPreviewableLinks(event)) {
m_linkPreviewers[eventId] = QSharedPointer<LinkPreviewer>(new LinkPreviewer(m_currentRoom, event));
}
m_linkPreviewers.remove(eventId);
}
// ReactionModel handles updates to add and remove reactions, we only need to

View File

@@ -11,6 +11,7 @@
#include <Quotient/csapi/pushrules.h>
#include <Quotient/jobs/basejob.h>
#include "controller.h"
#include "neochatconfig.h"
#include <KLazyLocalizedString>

View File

@@ -23,7 +23,7 @@ ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, const NeoC
{
if (m_event != nullptr && m_room != nullptr) {
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
if (m_event && m_event->id() == eventId) {
if (m_event->id() == eventId) {
updateReactions();
}
});
@@ -80,7 +80,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
"%2 reacted with %3",
reaction.authors.count(),
text,
m_shortcodes.contains(reaction.reaction) ? m_shortcodes[reaction.reaction] : reactionText(reaction.reaction));
reactionText(reaction.reaction));
return text;
}
@@ -111,7 +111,6 @@ void ReactionModel::updateReactions()
beginResetModel();
m_reactions.clear();
m_shortcodes.clear();
const auto &annotations = m_room->relatedEvents(*m_event, Quotient::EventRelation::AnnotationType);
if (annotations.isEmpty()) {
@@ -126,9 +125,6 @@ void ReactionModel::updateReactions()
}
if (const auto &e = eventCast<const Quotient::ReactionEvent>(a)) {
reactions[e->key()].append(m_room->user(e->senderId()));
if (e->contentJson()[QStringLiteral("shortcode")].toString().length()) {
m_shortcodes[e->key()] = e->contentJson()[QStringLiteral("shortcode")].toString().toHtmlEscaped();
}
}
}
@@ -162,18 +158,8 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
};
}
QString ReactionModel::reactionText(QString text) const
QString ReactionModel::reactionText(const QString &text)
{
text = text.toHtmlEscaped();
if (text.startsWith(QStringLiteral("mxc://"))) {
static QFont font;
static int size = font.pixelSize();
if (size == -1) {
size = font.pointSizeF() * 1.333;
}
return QStringLiteral("<img src=\"%1\" width=\"%2\" height=\"%2\">")
.arg(m_room->connection()->makeMediaUrl(QUrl(text)).toString(), QString::number(size));
}
const auto isEmoji = [](const QString &text) {
#ifdef HAVE_ICU
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);

View File

@@ -21,8 +21,6 @@ class User;
class ReactionModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
@@ -71,9 +69,8 @@ private:
const NeoChatRoom *m_room;
const Quotient::RoomMessageEvent *m_event;
QList<Reaction> m_reactions;
QMap<QString, QString> m_shortcodes;
void updateReactions();
QString reactionText(QString text) const;
static QString reactionText(const QString &text);
};
Q_DECLARE_METATYPE(ReactionModel *)

View File

@@ -3,12 +3,15 @@
#include "roomlistmodel.h"
#include "controller.h"
#include "eventhandler.h"
#include "neochatconfig.h"
#include "neochatroom.h"
#include "roommanager.h"
#include "spacehierarchycache.h"
#include <Quotient/user.h>
#include <QDebug>
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
@@ -20,6 +23,7 @@
#include <KLocalizedString>
#include <QGuiApplication>
#include <utility>
using namespace Quotient;
@@ -345,7 +349,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
return m_categoryVisibility.value(data(index, CategoryRole).toInt(), true);
}
if (role == SubtitleTextRole) {
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
if (room->lastEventIsSpoiler()) {
return QString();
}
EventHandler eventHandler;

View File

@@ -3,6 +3,8 @@
#pragma once
#include <Quotient/events/roomevent.h>
#include <QAbstractListModel>
#include <QQmlEngine>

View File

@@ -136,10 +136,6 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
return eventHandler.isHighlighted();
case EventIdRole:
return eventHandler.getId();
case IsThreadedRole:
return eventHandler.isThreaded();
case ThreadRootRole:
return eventHandler.threadRoot();
}
return DelegateType::Message;
}
@@ -185,8 +181,6 @@ QHash<int, QByteArray> SearchModel::roleNames() const
{MimeTypeRole, "mimeType"},
{ShowLinkPreviewRole, "showLinkPreview"},
{LinkPreviewRole, "linkPreview"},
{IsThreadedRole, "isThreaded"},
{ThreadRootRole, "threadRoot"},
};
}

View File

@@ -85,8 +85,6 @@ public:
MimeTypeRole,
ShowLinkPreviewRole,
LinkPreviewRole,
IsThreadedRole,
ThreadRootRole,
};
Q_ENUM(Roles)
explicit SearchModel(QObject *parent = nullptr);

View File

@@ -3,6 +3,8 @@
#include "serverlistmodel.h"
#include "controller.h"
#include <Quotient/connection.h>
#include <QDebug>
@@ -11,8 +13,6 @@
#include <KConfigGroup>
#include <KSharedConfig>
#include "neochatconnection.h"
ServerListModel::ServerListModel(QObject *parent)
: QAbstractListModel(parent)
{

View File

@@ -7,7 +7,7 @@
#include <Quotient/jobs/basejob.h>
#include <Quotient/room.h>
#include "neochatconnection.h"
#include "controller.h"
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
: QAbstractItemModel(parent)

View File

@@ -3,7 +3,7 @@
#include "spacetreeitem.h"
#include "neochatconnection.h"
#include "controller.h"
SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection,
SpaceTreeItem *parent,

View File

@@ -76,7 +76,6 @@ 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
Comment[ta]=மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி
Comment[tr]=Merkezi olmayan iletişim protokolü Matrix için bir istemci
Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними
Comment[x-test]=xxA client for matrix, the decentralized communication protocolxx
@@ -182,7 +181,6 @@ Name[eu]=Gonbidapen berria
Name[fi]=Uusi kutsu
Name[fr]=Nouvelle invitation
Name[gl]=Novo convite
Name[hu]=Új meghívó
Name[ia]=Nove invitation
Name[id]=Undangan Baru
Name[ie]=Nov invitation
@@ -219,7 +217,6 @@ Comment[eu]=Gela baterako gonbidapen berri bat dago
Comment[fi]=Uusi kutsu huoneeseen
Comment[fr]=Il y a une nouvelle invitation dans un salon.
Comment[gl]=Tes un novo convite para unha sala.
Comment[hu]=Új meghívó érkezett egy szobába
Comment[ia]=Il ha un nove invitation a un sala
Comment[id]=Ada undangan baru ke sebuah ruangan
Comment[ie]=Vu have un nov invitation a un chambre

View File

@@ -11,6 +11,9 @@
<entry name="OpenRoom" type="String">
<label>Latest opened room</label>
</entry>
<entry name="ActiveConnection" type="String">
<label>Latest active connection</label>
</entry>
<entry name="ColorScheme" type="String">
<label>Color scheme</label>
</entry>
@@ -156,11 +159,5 @@
<default></default>
</entry>
</group>
<group name="FeatureFlags">
<entry name="Threads" type="bool">
<label>Enable threads</label>
<default>false</default>
</entry>
</group>
</kcfg>

View File

@@ -44,11 +44,6 @@ NeoChatConnection::NeoChatConnection(QObject *parent)
connect(this, &NeoChatConnection::networkError, this, [this]() {
setIsOnline(false);
});
connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
if (job->error() == BaseJob::UserConsentRequired) {
Q_EMIT userConsentRequired(job->errorUrl());
}
});
}
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)

View File

@@ -99,7 +99,6 @@ Q_SIGNALS:
void labelChanged();
void isOnlineChanged();
void passwordStatus(NeoChatConnection::PasswordStatus status);
void userConsentRequired(QUrl url);
private:
bool m_isOnline = true;

View File

@@ -4,10 +4,15 @@
#include "neochatroom.h"
#include <QFileInfo>
#include <QGuiApplication>
#include <QMetaObject>
#include <QMimeDatabase>
#include <QPalette>
#include <QTemporaryFile>
#include <QTextDocument>
#include <QMediaMetaData>
#include <QMediaPlayer>
#include <QMimeDatabase>
#include <QTemporaryFile>
#include <Quotient/jobs/basejob.h>
#include <Quotient/user.h>
@@ -30,11 +35,13 @@
#include <Quotient/events/roommemberevent.h>
#include <Quotient/events/roompowerlevelsevent.h>
#include <Quotient/events/simplestateevents.h>
#include <Quotient/events/stickerevent.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include "chatbarcache.h"
#include "clipboard.h"
#include "controller.h"
#include "eventhandler.h"
#include "events/joinrulesevent.h"
#include "events/pollevent.h"
@@ -46,6 +53,8 @@
#include "urlhelper.h"
#include "utils.h"
#include <KConfig>
#include <KConfigGroup>
#ifndef Q_OS_ANDROID
#include <KIO/Job>
#include <KIO/JobTracker>
@@ -114,7 +123,6 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
Q_EMIT parentIdsChanged();
Q_EMIT canonicalParentChanged();
Q_EMIT joinRuleChanged();
Q_EMIT readOnlyChanged();
});
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
connect(this, &Room::changed, this, [this]() {
@@ -663,11 +671,6 @@ bool NeoChatRoom::isInvite() const
return joinState() == JoinState::Invite;
}
bool NeoChatRoom::readOnly() const
{
return !canSendEvent("m.room.message"_ls);
}
bool NeoChatRoom::isUserBanned(const QString &user) const
{
auto roomMemberEvent = currentState().get<RoomMemberEvent>(user);

View File

@@ -133,11 +133,6 @@ class NeoChatRoom : public Quotient::Room
*/
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
/**
* @brief Whether the local user can send messages in the room.
*/
Q_PROPERTY(bool readOnly READ readOnly NOTIFY readOnlyChanged)
/**
* @brief The current join rule for the room as a QString.
*
@@ -557,8 +552,6 @@ public:
bool isInvite() const;
bool readOnly() const;
Q_INVOKABLE void clearInvitationNotification();
[[nodiscard]] QString joinRule() const;
@@ -816,7 +809,6 @@ Q_SIGNALS:
void canonicalParentChanged();
void lastActiveTimeChanged();
void isInviteChanged();
void readOnlyChanged();
void displayNameChanged();
void pushNotificationStateChanged(PushNotificationState::State state);
void showMessage(MessageType messageType, const QString &message);

View File

@@ -21,6 +21,7 @@
#endif
#include "controller.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "roommanager.h"

View File

@@ -56,7 +56,6 @@ Comment[eu]=Bilatu gelak NeoChat-en
Comment[fi]=Etsi huoneita NeoChatissä
Comment[fr]=Trouver des salons dans NeoChat
Comment[gl]=Atopa salas en NeoChat.
Comment[hu]=Szobák keresése a NeoChatben
Comment[ia]=Trova salas in NeoChat
Comment[id]=Cari ruangan di NeoChat
Comment[ie]=Trovar chambres in NeoChat

View File

@@ -61,7 +61,7 @@ FormCard.FormCardPage {
text: i18n("Upload new avatar")
display: QQC2.AbstractButton.IconOnly
onClicked: parent.clicked()
onClicked: parent.onClicked()
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered

View File

@@ -42,8 +42,7 @@ MessageDelegate {
bubbleContent: ColumnLayout {
MediaPlayer {
id: audio
onErrorOccurred: (error, errorString) => console.warn("Audio playback error:" + error + errorString)
audioOutput: AudioOutput {}
source: root.progressInfo.localPath
}
states: [
@@ -74,19 +73,18 @@ MessageDelegate {
},
State {
name: "paused"
when: root.progressInfo.completed && (audio.playbackState === MediaPlayer.StoppedState || audio.playbackState === MediaPlayer.PausedState)
when: root.progressInfo.completed && (audio.playbackState === Audio.StoppedState || audio.playbackState === Audio.PausedState)
PropertyChanges {
target: playButton
icon.name: "media-playback-start"
onClicked: {
audio.source = root.progressInfo.localPath;
audio.play();
audio.play()
}
}
},
State {
name: "playing"
when: root.progressInfo.completed && audio.playbackState === MediaPlayer.PlayingState
when: root.progressInfo.completed && audio.playbackState === Audio.PlayingState
PropertyChanges {
target: playButton

View File

@@ -220,7 +220,7 @@ QQC2.Control {
x: textField.cursorRectangle.x
y: textField.cursorRectangle.y - height
onFormattingSelected: _private.formatText(format, selectionStart, selectionEnd)
onFormattingSelected: root.formatText(format, selectionStart, selectionEnd)
}
Keys.onDeletePressed: {

View File

@@ -7,7 +7,6 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
import org.kde.neochat.config
/**
* @brief A component that provides a set of actions when a message is hovered in the timeline.
@@ -90,7 +89,7 @@ QQC2.Control {
onTriggered: emojiDialog.open()
},
Kirigami.Action {
visible: root.delegate && root.delegate.author.isLocalUser && (root.delegate.delegateType === DelegateType.Emote || root.delegate.delegateType === DelegateType.Message) && !root.currentRoom.readOnly
visible: root.delegate && root.delegate.author.isLocalUser && (root.delegate.delegateType === DelegateType.Emote || root.delegate.delegateType === DelegateType.Message)
text: i18n("Edit")
icon.name: "document-edit"
onTriggered: {
@@ -99,7 +98,6 @@ QQC2.Control {
}
},
Kirigami.Action {
visible: !root.currentRoom.readOnly
text: i18n("Reply")
icon.name: "mail-replied-symbolic"
onTriggered: {
@@ -109,7 +107,6 @@ QQC2.Control {
}
},
Kirigami.Action {
visible: Config.threads && !root.currentRoom.readOnly
text: i18n("Reply in Thread")
icon.name: "dialog-messages"
onTriggered: {

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