Compare commits

...

58 Commits

Author SHA1 Message Date
Tobias Fella
4984181613 Refactor and improve emoji detection in reactions
UCHAR_EMOJI_PRESENTATION is replaced with UCHAR_EMOJI, which seems to be more useful for decting emojis.
The lambda is also turned into a proper function as there's no apparent reason for it to be a lambda.
2024-03-19 20:51:50 +01:00
Tobias Fella
af75136269 Bump compiler settings level to 6.0 2024-03-19 20:05:23 +01:00
l10n daemon script
c8eb75a148 GIT_SILENT Sync po/docbooks with svn 2024-03-19 01:22:10 +00:00
Joshua Goins
5109b4fcd1 Fix the quick format bar not actually doing anything 2024-03-18 20:17:06 +00:00
Joshua Goins
1b7f482d0b Exclude lonely question marks from the linkify regex
Many URLs we see in the KDE rooms end with a question mark, without a
space. The linkify regex for plain URLs incorrectly considered them as
part of the link, which usually breaks them when opened in a web
browser. Now the regex excludes these, unless they are accompanied by
another character (so links like kde.org/realurl?is=true will still
work.)
2024-03-18 15:46:22 -04:00
James Graham
6f9a273d39 Timeline Module
Move all the timeline QML files into their own QML module. Having them all in the same location is annoying and hard to work with.
2024-03-18 18:39:59 +00:00
l10n daemon script
51d354a9c8 GIT_SILENT Sync po/docbooks with svn 2024-03-18 01:31:43 +00:00
l10n daemon script
40b2b9554b SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-03-18 01:13:51 +00:00
l10n daemon script
ba1aca84ff GIT_SILENT Sync po/docbooks with svn 2024-03-17 01:30:01 +00:00
James Graham
17688a49d5 Remove stray log 2024-03-16 13:33:37 +00:00
James Graham
5ff199cc3e Itinerary Component
Move the itinerary model representation to it's own component and instantiate from MessageComponentModel. This starts to lay some groundwork for previewing other files.
2024-03-16 09:28:30 +00:00
l10n daemon script
81a79105d7 GIT_SILENT Sync po/docbooks with svn 2024-03-16 01:34:25 +00:00
l10n daemon script
e39760ccfb SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-03-16 01:15:17 +00:00
Heiko Becker
1c43da2532 GIT_SILENT Update Appstream for new release
(cherry picked from commit f731877519)
2024-03-15 22:12:56 +01:00
Joshua Goins
2846def00f Fix typo in MessageEditComponent 2024-03-15 15:12:46 -04:00
Joshua Goins
e2eb6ab33c Don't destroy formatting when editing previous messages
Adds a few new methods to grab the markdown/slightly rich text from the
message, and will intelligently re-insert user mentions as needed.
2024-03-15 14:54:06 -04:00
Joshua Goins
35b08d085c Prevent collision between KUnifiedPush DBus and KRunner DBus
These share the same D-Bus service name (org.kde.neochat) which comes
with a fun little addition: KRunner activation! While this is not a
problem while NeoChat is running - since it's already registered - this
becomes an issue while searching for NeoChat in something like the
Kickoff. The Kickoff (and consequently, KRunner) tries to activate the
NeoChat D-Bus service which runs our unified push parts.

This introduces a "FakeRunner" which watches closely for calls to the
KRunner interface while we're in unified push mode (or directly called
from D-Bus but not running) so it quits immediately.
2024-03-15 18:41:04 +00:00
Joshua Goins
064b0581a7 Make the tabs in developer tools full-width
It looks slightly better, and since there's only four tabs it increases
the tappable area for them.
2024-03-15 18:39:21 +00:00
l10n daemon script
0cc38aa69a GIT_SILENT Sync po/docbooks with svn 2024-03-15 01:31:04 +00:00
l10n daemon script
8312483659 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-03-15 01:13:30 +00:00
l10n daemon script
0ceb0b4421 GIT_SILENT Sync po/docbooks with svn 2024-03-14 01:32:04 +00:00
l10n daemon script
cc373365fb SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-03-14 01:14:11 +00:00
l10n daemon script
75d9b6e2a1 GIT_SILENT Sync po/docbooks with svn 2024-03-13 01:20:21 +00:00
l10n daemon script
78fa38ba68 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-03-13 01:15:08 +00:00
l10n daemon script
0b28712a34 GIT_SILENT made messages (after extraction) 2024-03-13 00:38:07 +00:00
Tobias Fella
48937c8d9a Make sure that timeline is scrolled to end when switching room 2024-03-12 22:45:29 +01:00
l10n daemon script
8c966a5e1a GIT_SILENT Sync po/docbooks with svn 2024-03-12 01:18:54 +00:00
l10n daemon script
f49dd371b7 GIT_SILENT made messages (after extraction) 2024-03-12 00:37:52 +00:00
Tobias Fella
6947fbc12a Add purpose plugin
Implements #182
2024-03-11 20:07:00 +01:00
Nicolas Fella
550dc43dc0 Remove manual window toggling for system tray icon
KStatusNotifierItem automatically does this for us
since we associate our window with it

Doing it again causes the window to be toggled again, which means
it won't be shown

BUG: 479721

BUG: 482779
2024-03-11 11:31:49 +01:00
l10n daemon script
23c9a4fea7 GIT_SILENT Sync po/docbooks with svn 2024-03-11 01:18:43 +00:00
Carl Schwan
7d26f3351f Fix crash in RoomTreeModel 2024-03-10 19:15:05 +01:00
Tobias Fella
b546554fef Require frameworks 6.0 2024-03-10 14:30:49 +01:00
James Graham
cc058a7cd3 Re-order spaces by dragging and dropping
Title
2024-03-10 11:18:28 +00:00
James Graham
7654b83339 Move the devtools button to UserInfo
Makes more sense now that room is selectable anyway and allows access from space home pages
2024-03-10 10:55:05 +00:00
James Graham
93426546ad Make sure that the MessageSourceSheet component is created properly in devtools 2024-03-10 10:25:53 +00:00
l10n daemon script
23bc38ca6c GIT_SILENT Sync po/docbooks with svn 2024-03-10 01:30:55 +00:00
Tobias Fella
5ccce364d3 Allow opening the settings from the welcome page
This is required to configure a proxy before logging in
2024-03-09 11:49:18 +01:00
l10n daemon script
b488b55a71 GIT_SILENT Sync po/docbooks with svn 2024-03-09 01:23:06 +00:00
l10n daemon script
0bace17074 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-03-09 01:14:29 +00:00
Tobias Fella
ad6c7dbd1f Don't link KDBusAddons on windows 2024-03-08 22:01:55 +01:00
James Graham
2a6e63595e Fix space tree refresh
Stop space hierarchy being duplicated. This is done by making sure old jobs are cleared at all resets and to make doubly sure when a child is inserted it overrides itself so there can never be duplicates
2024-03-08 18:36:43 +00:00
l10n daemon script
4d62ad1938 GIT_SILENT Sync po/docbooks with svn 2024-03-08 01:18:17 +00:00
l10n daemon script
e7c3a24011 GIT_SILENT Sync po/docbooks with svn 2024-03-07 01:17:40 +00:00
l10n daemon script
37468607fe GIT_SILENT Sync po/docbooks with svn 2024-03-06 01:18:26 +00:00
Albert Astals Cid
5b007129e3 flatpak: Switch to non-preview runtime 2024-03-06 00:33:31 +01:00
Tobias Fella
93ceb4d49c Improve hover link indicator accessibility
It's a bit pointless, since hovering it requires a mouse... but it's better than the previous behavior
2024-03-05 19:14:31 +01:00
Tobias Fella
85b806fcba Fix appstream 2024-03-05 18:27:39 +01:00
l10n daemon script
20596aabb8 GIT_SILENT Sync po/docbooks with svn 2024-03-05 01:18:31 +00:00
l10n daemon script
825108c59e GIT_SILENT made messages (after extraction) 2024-03-05 00:37:50 +00:00
James Graham
09c31b20e6 stripBlockTags Fixes
Make the code more robust by accounting for things like tag attributes
2024-03-04 21:05:11 +00:00
James Graham
78271a3738 Add highlight and copy button to code component 2024-03-04 20:09:22 +00:00
James Graham
e029aaadfc No Code String Convert
No need to try and convert code strings anymore this is now handled in KSyntaxHighlighter

see frameworks/syntax-highlighting!603 and frameworks/syntax-highlighting!604
2024-03-04 18:05:25 +00:00
l10n daemon script
f6efa35ed2 GIT_SILENT Sync po/docbooks with svn 2024-03-04 01:18:21 +00:00
l10n daemon script
728bad00b4 GIT_SILENT made messages (after extraction) 2024-03-04 00:37:35 +00:00
James Graham
97f3013f7a Visualise readacted messages
Make sure that a text delegate is added for redacted messages so that a message can be shown when show deletions is on
2024-03-03 16:33:21 +00:00
Tobias Fella
269a832ac9 Fix binding loop in NotificationsView 2024-03-03 16:56:18 +01:00
Tobias Fella
3b5b7af531 Fix QML warning 2024-03-03 16:43:52 +01:00
126 changed files with 15013 additions and 13427 deletions

View File

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

View File

@@ -49,3 +49,7 @@ License: CC0-1.0
Files: appiumtests/data/*
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: CC0-1.0
Files: src/purpose/purposeplugin.json
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause

View File

@@ -14,7 +14,7 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
set(KF_MIN_VERSION "5.240.0")
set(KF_MIN_VERSION "6.0")
set(QT_MIN_VERSION "6.5")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
@@ -24,7 +24,7 @@ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(KDE_COMPILERSETTINGS_LEVEL 5.105)
set(KDE_COMPILERSETTINGS_LEVEL 6.0)
include(FeatureSummary)
include(ECMSetupVersion)
@@ -72,6 +72,10 @@ set_package_properties(KF6Kirigami PROPERTIES
)
find_package(KF6KirigamiAddons 0.7.2 REQUIRED)
if (UNIX AND NOT APPLE AND NOT ANDROID AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Purpose)
endif ()
if(ANDROID)
find_package(OpenSSL)
set_package_properties(OpenSSL PROPERTIES

View File

@@ -55,6 +55,7 @@ private Q_SLOTS:
void genericBody_data();
void genericBody();
void nullGenericBody();
void markdownBody();
void subtitle();
void nullSubtitle();
void mediaInfo();
@@ -293,6 +294,13 @@ void EventHandlerTest::nullGenericBody()
QCOMPARE(noEventHandler.getGenericBody(), QString());
}
void EventHandlerTest::markdownBody()
{
EventHandler eventHandler(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
}
void EventHandlerTest::subtitle()
{
EventHandler eventHandler(room, room->messageEvents().at(0).get());

View File

@@ -513,7 +513,7 @@ void TextHandlerTest::componentOutput_data()
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Code,
QStringLiteral("Some code"),
QVariantMap{{QStringLiteral("class"), QStringLiteral("HTML")}}}};
QVariantMap{{QStringLiteral("class"), QStringLiteral("html")}}}};
QTest::newRow("quote") << QStringLiteral("<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>")
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, QStringLiteral("Text"), {}},
MessageComponent{MessageComponentType::Quote, QStringLiteral("\"blockquote\""), {}}};

View File

@@ -59,6 +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="it">Conversa con i tuoi contatti su matrix</summary>
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
@@ -78,8 +79,11 @@
<p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
<p xml:lang="ca">El NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
<p xml:lang="ca-valencia">NeoChat és una aplicació de xat que us permet aprofitar plenament la xarxa Matrix. Proporciona una manera segura d'enviar missatges de text, vídeos i arxius d'àudio a la vostra família, companys i amics.</p>
<p xml:lang="eo">NeoChat estas babilej-apo, kiu ebligas al vi plene profiti de la Matrix-reto. Ĝi provizas al vi sekuran manieron sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj.</p>
<p xml:lang="es">NeoChat es una aplicación de chat que le permite aprovechar al máximo la red Matrix. Le proporciona un modo seguro de enviar mensajes de texto, vídeos y archivos de sonido a su familia, colegas y amigos.</p>
<p xml:lang="eu">NeoChat, Matrix sarearen abantaila guztiei probetsua ateratzeko aukera ematen dizun berriketa aplikaizo bat da. Zure familiari, kideei eta lagunei testu mezuak, bideoak eta audio fitxategiak era seguruan bidaltzeko aukera ematen dizu.</p>
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé denvoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p>
<p xml:lang="hu">A NeoChat egy olyan csevegőalkalmazás, amellyel teljes mértékben kihasználhatja a Matrix hálózatot. Biztonságos módot biztosít szöveges üzenetek, videók és hangfájlok küldéséhez családtagjainak, kollégáinak és barátainak.</p>
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
<p xml:lang="it">NeoChat è un'applicazione di chat che ti consente di sfruttare appieno la rete Matrix. Ti fornisce un modo sicuro per inviare messaggi di testo, video e file audio a familiari, colleghi e amici.</p>
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
@@ -89,6 +93,7 @@
<p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p>
<p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p>
<p xml:lang="x-test">xxNeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.xx</p>
<p xml:lang="zh-TW">NeoChat 是一個讓您能夠完全利用 Matrix 網路的聊天應用程式。它讓您安全地傳送文字訊息、影片或音訊檔給家人、同事或朋友等等。</p>
<p>NeoChat 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.</p>
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
@@ -100,6 +105,7 @@
<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,6 +131,7 @@
<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>
@@ -152,6 +159,7 @@
<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>
@@ -178,6 +186,7 @@
<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>
@@ -204,6 +213,7 @@
<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,8 +244,7 @@
<keyword>Matrix</keyword>
<keyword>Kirigami</keyword>
</keywords>
<developer>
<id>kde.org</id>
<developer id="kde.org">
<name>The KDE Community</name>
<url>https://kde.org</url>
</developer>
@@ -264,6 +273,7 @@
<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>
@@ -285,8 +295,11 @@
<caption>Discover new communities with Matrix Spaces</caption>
<caption xml:lang="ca">Descobriu comunitats noves amb els espais de Matrix</caption>
<caption xml:lang="ca-valencia">Descobriu comunitats noves amb els espais de Matrix</caption>
<caption xml:lang="eo">Malkovru novajn komunumojn per Matrix Spaces</caption>
<caption xml:lang="es">Descubra nuevas comunidades con los espacios de Matrix</caption>
<caption xml:lang="eu">Ezagutu komunitate berriak Matrixeko Tokiak erabiliz</caption>
<caption xml:lang="fr">Découvrez de nouvelles communautés avec les espaces sous Matrix</caption>
<caption xml:lang="hu">Fedezzen fel új közösségeket a Matrix Terek segítségével</caption>
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption>
<caption xml:lang="it">Scopri nuove comunità con Matrix Spaces</caption>
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
@@ -296,6 +309,7 @@
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption>
<caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption>
<caption xml:lang="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
<caption xml:lang="zh-TW">利用 Matrix 聊天空間發現新的社群</caption>
</screenshot>
<!--
Currently invalid. See https://github.com/ximion/appstream/issues/611
@@ -316,6 +330,7 @@
<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>
@@ -345,6 +360,7 @@
<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,6 +382,7 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<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>

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

@@ -3,6 +3,10 @@
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE)
add_subdirectory(purpose)
endif()
add_library(neochat STATIC
controller.cpp
controller.h
@@ -165,6 +169,8 @@ add_library(neochat STATIC
mediamanager.h
models/statekeysmodel.cpp
models/statekeysmodel.h
sharehandler.cpp
sharehandler.h
)
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
@@ -207,19 +213,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/RoomData.qml
qml/ServerData.qml
qml/EmojiPicker.qml
qml/TimelineDelegate.qml
qml/ReplyComponent.qml
qml/StateDelegate.qml
qml/MessageDelegate.qml
qml/Bubble.qml
qml/SectionDelegate.qml
qml/ReactionDelegate.qml
qml/EventDelegate.qml
qml/ReadMarkerDelegate.qml
qml/MimeComponent.qml
qml/StateComponent.qml
qml/MessageEditComponent.qml
qml/AvatarFlow.qml
qml/LoginStep.qml
qml/Login.qml
qml/Homeserver.qml
@@ -304,24 +297,10 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/SelectSpacesDialog.qml
qml/AttachDialog.qml
qml/NotificationsView.qml
qml/LoadingDelegate.qml
qml/TimelineEndDelegate.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
qml/UserSearchPage.qml
qml/ManualUserDialog.qml
qml/MessageComponentChooser.qml
qml/TextComponent.qml
qml/ImageComponent.qml
qml/VideoComponent.qml
qml/AudioComponent.qml
qml/EncryptedComponent.qml
qml/FileComponent.qml
qml/LocationComponent.qml
qml/LiveLocationComponent.qml
qml/PollComponent.qml
qml/LinkPreviewComponent.qml
qml/LoadComponent.qml
qml/RecommendedSpaceDialog.qml
qml/RoomTreeSection.qml
qml/DelegateContextMenu.qml
@@ -330,13 +309,13 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/IgnoredUsersDialog.qml
qml/AccountData.qml
qml/StateKeys.qml
qml/CodeComponent.qml
qml/QuoteComponent.qml
RESOURCES
qml/confetti.png
qml/glowdot.png
)
add_subdirectory(timeline)
if(UNIX)
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
else()
@@ -420,9 +399,14 @@ 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)
target_link_libraries(neochat PRIVATE timelineplugin)
target_link_libraries(neochat PUBLIC
Qt::Core
Qt::Quick
@@ -556,7 +540,7 @@ if(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF6::DBusAddons)
if(TARGET KF6::DBusAddons AND NOT WIN32)
target_link_libraries(neochat PUBLIC KF6::DBusAddons)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()

View File

@@ -3,6 +3,7 @@
#include "chatbarcache.h"
#include "chatdocumenthandler.h"
#include "eventhandler.h"
#include "neochatroom.h"
@@ -117,7 +118,7 @@ QString ChatBarCache::relationMessage() const
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
EventHandler eventhandler(room, &**event);
return eventhandler.getPlainBody();
return eventhandler.getMarkdownBody();
}
return {};
}
@@ -163,6 +164,54 @@ 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,8 +5,11 @@
#include <QObject>
#include <QQmlEngine>
#include <QQuickTextDocument>
#include <QTextCursor>
class ChatDocumentHandler;
/**
* @brief Defines a user mention in the current chat or edit text.
*/
@@ -174,6 +177,11 @@ 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

@@ -40,6 +40,7 @@ public:
Code, /**< A code section. */
Quote, /**< A quote section. */
File, /**< A message that is a file. */
Itinerary, /**< A preview for a file that can integrate with KDE itinerary.. */
Poll, /**< The initial event for a poll. */
Location, /**< A location event. */
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */

View File

@@ -280,6 +280,22 @@ 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()) {

View File

@@ -185,6 +185,13 @@ 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.
*

36
src/fakerunner.cpp Normal file
View File

@@ -0,0 +1,36 @@
// 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"

31
src/fakerunner.h Normal file
View File

@@ -0,0 +1,31 @@
// 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

@@ -11,6 +11,7 @@
#include <QQmlNetworkAccessManagerFactory>
#include <QQuickStyle>
#include <QQuickWindow>
#include <QtQml/QQmlExtensionPlugin>
#ifdef Q_OS_ANDROID
#include <QGuiApplication>
@@ -44,10 +45,16 @@
#include "neochatconfig.h"
#include "roommanager.h"
#include "windowcontroller.h"
#include "sharehandler.h"
#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
@@ -186,6 +193,9 @@ int main(int argc, char *argv[])
parser.addOption(dbusActivatedOption);
#endif
QCommandLineOption shareOption(QStringLiteral("share"), i18n("Share a URL to Matrix"), QStringLiteral("text"));
parser.addOption(shareOption);
about.setupCommandLine(&parser);
parser.process(app);
about.processCommandLine(&parser);
@@ -196,6 +206,14 @@ 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();
}
@@ -205,6 +223,8 @@ int main(int argc, char *argv[])
KDBusService service(KDBusService::Unique);
#endif
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
qml_register_types_org_kde_neochat();
qmlRegisterSingletonInstance("org.kde.neochat.config", 1, 0, "Config", NeoChatConfig::self());
qmlRegisterSingletonInstance("org.kde.neochat.accounts", 1, 0, "AccountRegistry", &Controller::instance().accounts());
@@ -215,26 +235,32 @@ int main(int argc, char *argv[])
#ifdef HAVE_KDBUSADDONS
service.connect(&service,
&KDBusService::activateRequested,
&RoomManager::instance(),
[&engine](const QStringList &arguments, const QString &workingDirectory) {
Q_UNUSED(workingDirectory);
&KDBusService::activateRequested,
&RoomManager::instance(),
[&engine](const QStringList &arguments, const QString &workingDirectory) {
Q_UNUSED(workingDirectory);
QWindow *window = windowFromEngine(&engine);
KWindowSystem::updateStartupId(window);
QWindow *window = windowFromEngine(&engine);
KWindowSystem::updateStartupId(window);
WindowController::instance().showAndRaiseWindow(QString());
WindowController::instance().showAndRaiseWindow(QString());
// Open matrix uri
if (arguments.isEmpty()) {
return;
}
auto args = arguments;
args.removeFirst();
for (const auto &arg : args) {
RoomManager::instance().resolveResource(arg);
}
});
// Open matrix uri
if (arguments.isEmpty()) {
return;
}
auto args = arguments;
args.removeFirst();
if (args.length() == 2 && args[0] == "--share"_ls) {
ShareHandler::instance().setText(args[1]);
return;
}
for (const auto &arg : args) {
RoomManager::instance().resolveResource(arg);
}
});
#endif
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
@@ -247,6 +273,10 @@ int main(int argc, char *argv[])
});
}
if (parser.isSet("share"_ls)) {
ShareHandler::instance().setText(parser.value(shareOption));
}
engine.addImageProvider(QLatin1String("mxc"), MatrixImageProvider::create(&engine, &engine));
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
@@ -255,7 +285,7 @@ int main(int argc, char *argv[])
return -1;
}
if (!parser.positionalArguments().isEmpty()) {
if (!parser.positionalArguments().isEmpty() && !parser.isSet("share"_ls)) {
RoomManager::instance().setUrlArgument(parser.positionalArguments()[0]);
}

View File

@@ -3,6 +3,7 @@
#include "itinerarymodel.h"
#include <QJsonDocument>
#include <QProcess>
#include "config-neochat.h"
@@ -16,20 +17,6 @@ ItineraryModel::ItineraryModel(QObject *parent)
{
}
void ItineraryModel::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
m_connection = connection;
Q_EMIT connectionChanged();
}
NeoChatConnection *ItineraryModel::connection() const
{
return m_connection;
}
QVariant ItineraryModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
@@ -133,11 +120,7 @@ QString ItineraryModel::path() const
void ItineraryModel::setPath(const QString &path)
{
if (path == m_path) {
return;
}
m_path = path;
Q_EMIT pathChanged();
loadData();
}

View File

@@ -4,19 +4,16 @@
#pragma once
#include <QAbstractListModel>
#include <QJsonArray>
#include <QPointer>
#include <QQmlEngine>
#include <QString>
#include "neochatconnection.h"
class ItineraryModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
QML_UNCREATABLE("")
public:
enum Roles {
@@ -37,9 +34,6 @@ public:
Q_ENUM(Roles)
explicit ItineraryModel(QObject *parent = nullptr);
void setConnection(NeoChatConnection *connection);
NeoChatConnection *connection() const;
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = {}) const override;
@@ -50,12 +44,7 @@ public:
Q_INVOKABLE void sendToItinerary();
Q_SIGNALS:
void connectionChanged();
void pathChanged();
private:
QPointer<NeoChatConnection> m_connection;
QJsonArray m_data;
QString m_path;
void loadData();

View File

@@ -64,11 +64,13 @@ MessageContentModel::MessageContentModel(const Quotient::RoomEvent *event, NeoCh
});
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
if (m_event != nullptr && eventId == m_event->id()) {
updateComponents();
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
}
});
connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) {
if (m_event != nullptr && eventId == m_event->id()) {
updateComponents();
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
}
});
@@ -152,6 +154,9 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue(m_room->fileTransferInfo(event->id()));
}
}
if (role == ItineraryModelRole) {
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
}
if (role == LatitudeRole) {
return eventHandler.getLatitude();
}
@@ -209,6 +214,7 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
roles[AuthorRole] = "author";
roles[MediaInfoRole] = "mediaInfo";
roles[FileTransferInfoRole] = "fileTransferInfo";
roles[ItineraryModelRole] = "itineraryModel";
roles[LatitudeRole] = "latitude";
roles[LongitudeRole] = "longitude";
roles[AssetRole] = "asset";
@@ -240,11 +246,19 @@ void MessageContentModel::updateComponents(bool isEditing)
if (isEditing) {
m_components += MessageComponent{MessageComponentType::Edit, QString(), {}};
} else if (m_event->isRedacted()) {
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
} else {
if (eventHandler.messageComponentType() == MessageComponentType::Text) {
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
auto body = EventHandler::rawMessageBody(*event);
m_components.append(TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced()));
} else if (eventHandler.messageComponentType() == MessageComponentType::File) {
m_components += MessageComponent{MessageComponentType::File, QString(), {}};
updateItineraryModel();
if (m_itineraryModel != nullptr) {
m_components += MessageComponent{MessageComponentType::Itinerary, QString(), {}};
}
} else {
m_components += MessageComponent{eventHandler.messageComponentType(), QString(), {}};
}
@@ -260,3 +274,25 @@ void MessageContentModel::updateComponents(bool isEditing)
endResetModel();
}
void MessageContentModel::updateItineraryModel()
{
if (m_room == nullptr || m_event == nullptr) {
return;
}
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
if (event->hasFileContent()) {
auto filePath = m_room->fileTransferInfo(event->id()).localPath;
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
delete m_itineraryModel;
m_itineraryModel = nullptr;
} else if (!filePath.isEmpty()) {
if (m_itineraryModel == nullptr) {
m_itineraryModel = new ItineraryModel(this);
}
m_itineraryModel->setPath(filePath.toString());
}
}
}
}

View File

@@ -8,6 +8,7 @@
#include "enums/messagecomponenttype.h"
#include "eventhandler.h"
#include "itinerarymodel.h"
#include "linkpreviewer.h"
#include "neochatroom.h"
@@ -45,6 +46,7 @@ public:
AuthorRole, /**< The author of the event. */
MediaInfoRole, /**< The media info for the event. */
FileTransferInfoRole, /**< FileTransferInfo for any downloading files. */
ItineraryModelRole, /**< The itinerary model for a file. */
LatitudeRole, /**< Latitude for a location event. */
LongitudeRole, /**< Longitude for a location event. */
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
@@ -92,4 +94,7 @@ private:
void updateComponents(bool isEditing = false);
LinkPreviewer *m_linkPreviewer = nullptr;
ItineraryModel *m_itineraryModel = nullptr;
void updateItineraryModel();
};

View File

@@ -162,6 +162,30 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
};
}
bool isEmoji(const QString &text)
{
#ifdef HAVE_ICU
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
int from = 0;
while (finder.toNextBoundary() != -1) {
auto to = finder.position();
if (text[from].isSpace()) {
from = to;
continue;
}
auto first = text.mid(from, to - from).toUcs4()[0];
if (!u_hasBinaryProperty(first, UCHAR_EMOJI)) {
return false;
}
from = to;
}
return true;
#else
return false;
#endif
}
QString ReactionModel::reactionText(QString text) const
{
text = text.toHtmlEscaped();
@@ -174,28 +198,6 @@ QString ReactionModel::reactionText(QString text) const
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);
int from = 0;
while (finder.toNextBoundary() != -1) {
auto to = finder.position();
if (text[from].isSpace()) {
from = to;
continue;
}
auto first = text.mid(from, to - from).toUcs4()[0];
if (!u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION)) {
return false;
}
from = to;
}
return true;
#else
return false;
#endif
};
return isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
}

View File

@@ -231,7 +231,7 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
// TODO room type changes
QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
return QVariant();
}

View File

@@ -8,6 +8,7 @@
#include <Quotient/room.h>
#include "neochatconnection.h"
#include "neochatroom.h"
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
: QAbstractItemModel(parent)
@@ -32,19 +33,13 @@ void SpaceChildrenModel::setSpace(NeoChatRoom *space)
}
// disconnect the new room signal from the old connection in case it is different.
if (m_space != nullptr) {
disconnect(m_space->connection(), &Quotient::Connection::loadedRoomState, this, nullptr);
m_space->connection()->disconnect(this);
m_space->disconnect(this);
}
m_space = space;
Q_EMIT spaceChanged();
for (auto job : m_currentJobs) {
if (job) {
job->abandon();
}
}
m_currentJobs.clear();
auto connection = m_space->connection();
connect(connection, &Quotient::Connection::loadedRoomState, this, [this](Quotient::Room *room) {
if (m_pendingChildren.contains(room->name())) {
@@ -66,6 +61,17 @@ bool SpaceChildrenModel::loading() const
void SpaceChildrenModel::refreshModel()
{
for (auto job : m_currentJobs) {
if (job) {
job->abandon();
}
}
m_currentJobs.clear();
if (m_space == nullptr) {
return;
}
beginResetModel();
m_replacedRooms.clear();
delete m_rootItem;
@@ -112,6 +118,11 @@ void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJ
if (!successorId.isEmpty()) {
m_replacedRooms += successorId;
}
if (dynamic_cast<NeoChatRoom *>(room)->isSpace()) {
connect(room, &Quotient::Room::changed, this, [this]() {
refreshModel();
});
}
}
if (children[i].childrenState.size() > 0) {
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(children[i].roomId, Quotient::none, Quotient::none, 1);
@@ -120,8 +131,7 @@ void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJ
insertChildren(job->rooms(), index(insertRow, 0, parent));
});
}
parentItem->insertChild(insertRow,
new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()),
parentItem->insertChild(new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()),
parentItem,
children[i].roomId,
children[i].name,

View File

@@ -60,4 +60,65 @@ bool SpaceChildSortFilterModel::filterAcceptsRow(int sourceRow, const QModelInde
return true;
}
void SpaceChildSortFilterModel::move(const QModelIndex &currentIndex, const QModelIndex &targetIndex)
{
const auto rootSpace = dynamic_cast<SpaceChildrenModel *>(sourceModel())->space();
if (rootSpace == nullptr) {
return;
}
const auto connection = rootSpace->connection();
const auto currentParent = currentIndex.parent();
auto targetParent = targetIndex.parent();
NeoChatRoom *currentParentSpace = nullptr;
if (!currentParent.isValid()) {
currentParentSpace = rootSpace;
} else {
currentParentSpace = static_cast<NeoChatRoom *>(connection->room(currentParent.data(SpaceChildrenModel::RoomIDRole).toString()));
}
NeoChatRoom *targetParentSpace = nullptr;
if (!targetParent.isValid()) {
targetParentSpace = rootSpace;
} else {
targetParentSpace = static_cast<NeoChatRoom *>(connection->room(targetParent.data(SpaceChildrenModel::RoomIDRole).toString()));
}
// If both parents are not resolvable to a room object we don't have the permissions
// required for this action.
if (currentParentSpace == nullptr || targetParentSpace == nullptr) {
return;
}
const auto currentRow = currentIndex.row();
auto targetRow = targetIndex.row();
const auto moveRoomId = currentIndex.data(SpaceChildrenModel::RoomIDRole).toString();
auto targetRoom = static_cast<NeoChatRoom *>(connection->room(targetIndex.data(SpaceChildrenModel::RoomIDRole).toString()));
// If the target room is a space, assume we want to drop the room into it.
if (targetRoom != nullptr && targetRoom->isSpace()) {
targetParent = targetIndex;
targetParentSpace = targetRoom;
targetRow = rowCount(targetParent);
}
const auto newRowCount = rowCount(targetParent) + (currentParentSpace != targetParentSpace ? 1 : 0);
for (int i = 0; i < newRowCount; i++) {
if (currentParentSpace == targetParentSpace && i == currentRow) {
continue;
}
targetParentSpace->setChildOrder(index(i, 0, targetParent).data(SpaceChildrenModel::RoomIDRole).toString(),
QString::number(i > targetRow ? i + 1 : i, 36));
if (i == targetRow) {
if (currentParentSpace != targetParentSpace) {
currentParentSpace->removeChild(moveRoomId, true);
targetParentSpace->addChild(moveRoomId, true, false, false, QString::number(i + 1, 36));
} else {
targetParentSpace->setChildOrder(currentIndex.data(SpaceChildrenModel::RoomIDRole).toString(), QString::number(i + 1, 36));
}
}
}
}
#include "moc_spacechildsortfiltermodel.cpp"

View File

@@ -46,6 +46,8 @@ protected:
*/
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
Q_INVOKABLE void move(const QModelIndex &currentIndex, const QModelIndex &targetIndex);
Q_SIGNALS:
void filterTextChanged();

View File

@@ -37,6 +37,11 @@ SpaceTreeItem::~SpaceTreeItem()
qDeleteAll(m_children);
}
bool SpaceTreeItem::operator==(const SpaceTreeItem &other) const
{
return m_id == other.id();
}
SpaceTreeItem *SpaceTreeItem::child(int number)
{
if (number < 0 || number >= m_children.size()) {
@@ -50,12 +55,20 @@ int SpaceTreeItem::childCount() const
return m_children.count();
}
bool SpaceTreeItem::insertChild(int row, SpaceTreeItem *newChild)
bool SpaceTreeItem::insertChild(SpaceTreeItem *newChild)
{
if (row < 0 || row > m_children.size()) {
if (newChild == nullptr) {
return false;
}
m_children.insert(row, newChild);
for (auto it = m_children.begin(), end = m_children.end(); it != end; ++it) {
if (*it == newChild) {
*it = newChild;
return true;
}
}
m_children.append(newChild);
return true;
}

View File

@@ -35,6 +35,8 @@ public:
Quotient::StateEvents childStates = {});
~SpaceTreeItem();
bool operator==(const SpaceTreeItem &other) const;
/**
* @brief Return the child at the given row number.
*
@@ -48,9 +50,9 @@ public:
int childCount() const;
/**
* @brief Insert the given child at the given row number.
* @brief Insert the given child.
*/
bool insertChild(int row, SpaceTreeItem *newChild);
bool insertChild(SpaceTreeItem *newChild);
/**
* @brief Remove the child at the given row number.

View File

@@ -76,6 +76,7 @@ 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
@@ -253,12 +254,14 @@ Name[eo]=Kundividi
Name[es]=Compartir
Name[eu]=Partekatu
Name[fr]=Partager
Name[hu]=Megosztás
Name[ia]=Comparti
Name[it]=Condivisione
Name[ka]=გაზიარება
Name[nl]=Gedeelde
Name[pl]=Udostępnij
Name[sl]=Deli
Name[ta]=பகிர்
Name[tr]=Paylaş
Name[uk]=Оприлюднення
Name[x-test]=xxSharexx
@@ -270,12 +273,14 @@ Comment[eo]=La rezulto el kundividado de enhavero
Comment[es]=El resultado de compartir una parte de contenido
Comment[eu]=Eduki pieza bat partekatzearen emaitza
Comment[fr]=Le résultat du partage d'une partie de contenu.
Comment[hu]=Tartalom megosztásának eredménye
Comment[ia]=Le exito de compartir un pecietta de contento
Comment[it]=Il risultato della condivisione di un contenuto
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
Comment[nl]=Het resultaat van het delen van een stukje inhoud
Comment[pl]=Wynik udostępniania kawałka treści
Comment[sl]=Rezultat deljenega kosa vsebine
Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவு
Comment[tr]=Bir parça içerik paylaşımının sonucu
Comment[uk]=Результат оприлюднення даних
Comment[x-test]=xxThe result of sharing a piece of contentxx

View File

@@ -1329,7 +1329,7 @@ bool NeoChatRoom::childrenHaveHighlightNotifications() const
return SpaceHierarchyCache::instance().spaceHasHighlightNotifications(id());
}
void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical, bool suggested)
void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical, bool suggested, const QString &order)
{
if (!isSpace()) {
return;
@@ -1337,7 +1337,9 @@ void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool can
if (!canSendEvent("m.space.child"_ls)) {
return;
}
setState("m.space.child"_ls, childId, QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}, {"suggested"_ls, suggested}});
setState("m.space.child"_ls,
childId,
QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}, {"suggested"_ls, suggested}, {"order"_ls, order}});
if (setChildParent) {
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
@@ -1403,6 +1405,28 @@ void NeoChatRoom::toggleChildSuggested(const QString &childId)
}
}
void NeoChatRoom::setChildOrder(const QString &childId, const QString &order)
{
if (!isSpace()) {
return;
}
if (!canSendEvent("m.space.child"_ls)) {
return;
}
if (const auto childEvent = currentState().get("m.space.child"_ls, childId)) {
auto content = childEvent->contentJson();
if (!content.contains("via"_ls)) {
return;
}
if (content.value("order"_ls).toString() == order) {
return;
}
content.insert("order"_ls, order);
setState("m.space.child"_ls, childId, content);
}
}
PushNotificationState::State NeoChatRoom::pushNotificationState() const
{
return m_currentPushNotificationState;

View File

@@ -560,7 +560,7 @@ public:
* Will fail if the user doesn't have the required privileges or this room is
* not a space.
*/
Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false, bool canonical = false, bool suggested = false);
Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false, bool canonical = false, bool suggested = false, const QString &order = {});
/**
* @brief Remove the given room as a child.
@@ -583,6 +583,8 @@ public:
*/
Q_INVOKABLE void toggleChildSuggested(const QString &childId);
void setChildOrder(const QString &childId, const QString &order = {});
bool isInvite() const;
bool readOnly() const;

View File

@@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
kcoreaddons_add_plugin(neochatplugin SOURCES purposeplugin.cpp INSTALL_NAMESPACE "kf6/purpose")
target_link_libraries(neochatplugin
Qt::DBus
KF6::Purpose
KF6::KIOGui
)

View File

@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.1-or-later
#include <KIO/CommandLauncherJob>
#include <KPluginFactory>
#include <Purpose/PluginBase>
class NeoChatJob : public Purpose::Job
{
Q_OBJECT
public:
explicit NeoChatJob(QObject *parent)
: Purpose::Job(parent)
{
}
QStringList arrayToList(const QJsonArray &array)
{
QStringList ret;
for (const auto &val : array) {
ret += val.toString();
}
return ret;
}
void start() override
{
const QJsonArray urlsJson = data().value(QStringLiteral("urls")).toArray();
const QString title = data().value(QStringLiteral("title")).toString();
const QString message = QStringLiteral("%1 - %2").arg(title, arrayToList(urlsJson).join(QLatin1Char(' ')));
auto *job = new KIO::CommandLauncherJob(QStringLiteral("neochat"), {QStringLiteral("--share"), message});
connect(job, &KJob::finished, this, &NeoChatJob::emitResult);
job->start();
}
};
class Q_DECL_EXPORT PurposePlugin : public Purpose::PluginBase
{
Q_OBJECT
public:
PurposePlugin(QObject *p, const QVariantList &)
: Purpose::PluginBase(p)
{
}
Purpose::Job *createJob() const override
{
return new NeoChatJob(nullptr);
}
};
K_PLUGIN_CLASS_WITH_JSON(PurposePlugin, "purposeplugin.json")
#include "purposeplugin.moc"

View File

@@ -0,0 +1,60 @@
{
"KPlugin": {
"Authors": [
{
"Name": "Tobias Fella",
"Name[ca@valencia]": "Tobias Fella",
"Name[ca]": "Tobias Fella",
"Name[es]": "Tobias Fella",
"Name[fr]": "Tobias Fella",
"Name[hu]": "Tobias Fella",
"Name[ia]": "Tobias Fella",
"Name[it]": "Tobias Fella",
"Name[ka]": "Tobias Fella",
"Name[nl]": "Tobias Fella",
"Name[pl]": "Tobias Fella",
"Name[sl]": "Tobias Fella",
"Name[tr]": "Tobias Fella",
"Name[uk]": "Tobias Fella",
"Name[x-test]": "xxTobias Fellaxx"
}
],
"Category": "Utilities",
"Description": "Share via NeoChat",
"Description[ca@valencia]": "Compartix a través de NeoChat",
"Description[ca]": "Comparteix a través del NeoChat",
"Description[es]": "Compartir mediante NeoChat",
"Description[fr]": "Partager grâce à NeoChat",
"Description[hu]": "Megosztás NeoChatben",
"Description[ia]": "Comparti via NeoChat",
"Description[it]": "Condividi tramite NeoChat",
"Description[ka]": "გააზიარეთ NeoChat-ით",
"Description[nl]": "Delen via NeoChat",
"Description[pl]": "Udostępnij przez NeoChat",
"Description[sl]": "Deli prek NeoChat",
"Description[tr]": "NeoChat ile Paylaş",
"Description[uk]": "Оприлюднити за допомогою NeoChat",
"Description[x-test]": "xxShare via NeoChatxx",
"Icon": "org.kde.neochat",
"License": "GPL",
"Name": "NeoChat",
"Name[ca@valencia]": "NeoChat",
"Name[ca]": "NeoChat",
"Name[es]": "NeoChat",
"Name[fr]": "NeoChat",
"Name[hu]": "NeoChat",
"Name[ia]": "Neochat",
"Name[it]": "NeoChat",
"Name[ka]": "NeoChat",
"Name[nl]": "NeoChat",
"Name[pl]": "NeoChat",
"Name[sl]": "NeoChat",
"Name[tr]": "NeoChat",
"Name[uk]": "NeoChat",
"Name[x-test]": "xxNeoChatxx",
"X-Purpose-ActionDisplay": "NeoChat"
},
"X-Purpose-PluginTypes": [
"ShareUrl"
]
}

View File

@@ -23,7 +23,7 @@ ColumnLayout {
model: root.connection.accountDataEventTypes
delegate: FormCard.FormButtonDelegate {
text: modelData
onClicked: applicationWindow().pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/MessageSourceSheet.qml", {
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet.qml'), {
sourceText: root.connection.accountDataJsonString(modelData)
}, {
title: i18nc("@title:window", "Event Source"),

View File

@@ -34,7 +34,25 @@ QQC2.Control {
onActiveFocusChanged: textField.forceActiveFocus()
onCurrentRoomChanged: _private.chatBarCache = currentRoom.mainCache
onCurrentRoomChanged: {
_private.chatBarCache = currentRoom.mainCache
if (ShareHandler.text.length > 0 && ShareHandler.room === root.currentRoom.id) {
textField.text = ShareHandler.text;
ShareHandler.text = "";
ShareHandler.room = "";
}
}
Connections {
target: ShareHandler
function onRoomChanged(): void {
if (ShareHandler.text.length > 0 && ShareHandler.room === root.currentRoom.id) {
textField.text = ShareHandler.text;
ShareHandler.text = "";
ShareHandler.room = "";
}
}
}
/**
* @brief The ActionsHandler object to use.
@@ -223,7 +241,7 @@ QQC2.Control {
x: textField.cursorRectangle.x
y: textField.cursorRectangle.y - height
onFormattingSelected: root.formatText(format, selectionStart, selectionEnd)
onFormattingSelected: _private.formatText(format, selectionStart, selectionEnd)
}
Keys.onDeletePressed: {

View File

@@ -23,17 +23,27 @@ FormCard.FormCardPage {
header: QQC2.TabBar {
id: tabBar
readonly property real tabWidth: tabBar.width / tabBar.count
QQC2.TabButton {
text: qsTr("Room Data")
implicitWidth: tabBar.tabWidth
}
QQC2.TabButton {
text: qsTr("Server Info")
implicitWidth: tabBar.tabWidth
}
QQC2.TabButton {
text: i18nc("@title:tab", "Account Data")
implicitWidth: tabBar.tabWidth
}
QQC2.TabButton {
text: i18nc("@title:tab", "Feature Flags")
implicitWidth: tabBar.tabWidth
}
}

View File

@@ -13,7 +13,7 @@ import org.kde.neochat.config
FormCard.FormCardPage {
id: root
title: i18nc("@title:window", "General")
title: i18nc("@title:window", "Proxy")
property int currentType
property bool proxyConfigChanged: false

View File

@@ -35,7 +35,7 @@ Kirigami.ScrollablePage {
}
footer: Kirigami.PlaceholderMessage {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
text: i18n("Loading…")
visible: notificationsModel.nextToken.length > 0 && listView.count > 0
}

View File

@@ -30,7 +30,7 @@ ColumnLayout {
id: roomListModel
connection: root.connection
}
currentIndex: -1
currentIndex: 0
Component.onCompleted: currentIndex = roomListModel.rowForRoom(root.room)
onCurrentValueChanged: root.room = roomListModel.roomByAliasOrId(roomComboBox.currentValue)
}

View File

@@ -72,25 +72,6 @@ QQC2.ScrollView {
Layout.fillWidth: true
}
Delegates.RoundedItemDelegate {
id: devtoolsButton
icon.name: "tools"
text: i18n("Open developer tools")
visible: Config.developerTools && !root.room.isSpace
Layout.fillWidth: true
onClicked: {
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'DevtoolsPage.qml'), {
room: root.room,
connection: root.connection
}, {
title: i18n("Developer Tools")
});
}
}
Delegates.RoundedItemDelegate {
id: searchButton
visible: !root.room.isSpace

View File

@@ -136,7 +136,11 @@ Kirigami.Page {
if (root.spaceChanging) {
treeView.expandRecursively();
if (spaceDrawer.showDirectChats || spaceDrawer.selectedSpaceId.length < 1) {
RoomManager.resolveResource(treeView.itemAtIndex(treeView.index(1, 0)).currentRoom.id);
const item = treeView.itemAtIndex(treeView.index(1, 0))
if (!item) {
return;
}
RoomManager.resolveResource(item.currentRoom.id);
}
root.spaceChanging = false;
}

View File

@@ -7,6 +7,7 @@ import QtQuick.Layouts
import Qt.labs.qmlmodels
import org.kde.neochat
import org.kde.neochat.timeline
/**
* @brief Component for visualising the loaded media items in the room.

View File

@@ -4,6 +4,7 @@
import QtQuick
import org.kde.neochat
import org.kde.neochat.timeline
/**
* @brief Component for finding messages in a room.

View File

@@ -38,7 +38,7 @@ ColumnLayout {
}
FormCard.FormCard {
Repeater {
model: room.connection.getSupportedRoomVersions()
model: root.connection.getSupportedRoomVersions()
delegate: FormCard.FormTextDelegate {
text: modelData.id

View File

@@ -11,7 +11,7 @@ import org.kde.neochat
KirigamiSettings.CategorizedSettings {
id: root
required property NeoChatConnection connection
property NeoChatConnection connection
objectName: "settingsPage"
actions: [

View File

@@ -18,6 +18,7 @@ Item {
required property bool expanded
required property int hasChildren
required property int depth
required property int row
required property string roomId
required property string displayName
required property url avatarUrl
@@ -36,10 +37,17 @@ Item {
signal createRoom
Delegates.RoundedItemDelegate {
anchors.centerIn: root
id: mainDelegate
property int row: root.row
anchors.horizontalCenter: root.horizontalCenter
anchors.verticalCenter: root.verticalCenter
width: sizeHelper.currentWidth
highlighted: dropArea.containsDrag
contentItem: RowLayout {
z: 1
spacing: Kirigami.Units.largeSpacing
RowLayout {
@@ -140,15 +148,55 @@ Item {
}
}
TapHandler {
onTapped: {
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: mainDelegate
drag.axis: Drag.YAxis
drag.onActiveChanged: {
if (!dragArea.drag.active) {
mainDelegate.Drag.drop();
}
}
onClicked: {
if (root.isSpace) {
root.treeView.toggleExpanded(row);
root.treeView.toggleExpanded(root.row);
} else {
RoomManager.resolveResource(root.roomId, root.isJoined ? "" : "join");
}
}
}
states: [
State {
when: mainDelegate.Drag.active && root.parentRoom.canSendState("m.space.child")
ParentChange {
target: mainDelegate
parent: root.treeView
}
AnchorChanges {
target: mainDelegate
anchors.horizontalCenter: undefined
anchors.verticalCenter: undefined
}
}
]
Drag.active: dragArea.drag.active
Drag.hotSpot.x: mainDelegate.width / 2
Drag.hotSpot.y: Kirigami.Units.smallSpacing
}
DropArea {
id: dropArea
anchors.fill: parent
onDropped: (drag) => {
root.treeView.model.move(root.treeView.index(drag.source.row, 0), root.treeView.index(root.row, 0))
}
}
DelegateSizeHelper {

View File

@@ -31,7 +31,7 @@ FormCard.FormCardPage {
delegate: FormCard.FormButtonDelegate {
text: model.stateKey
onClicked: applicationWindow().pageStack.pushDialogLayer('qrc:/org/kde/neochat/qml/MessageSourceSheet.qml', {
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet.qml'), {
sourceText: stateKeysModel.stateEventJson(stateKeysModel.index(model.index, 0))
}, {
title: i18nc("@title:window", "Event Source"),

View File

@@ -13,6 +13,7 @@ import org.kde.kitemmodels
import org.kde.neochat
import org.kde.neochat.config
import org.kde.neochat.timeline
QQC2.ScrollView {
id: root
@@ -79,6 +80,10 @@ QQC2.ScrollView {
model: root.messageFilterModel
onCountChanged: if (root.roomChanging) {
root.positionViewAtBeginning();
}
Timer {
interval: 1000
running: messageListView.atYBeginning

View File

@@ -118,6 +118,22 @@ RowLayout {
Layout.alignment: Qt.AlignRight
visible: false
}
QQC2.ToolButton {
visible: Config.developerTools
icon.name: "tools"
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'DevtoolsPage.qml'), {
connection: root.connection
}, {
title: i18n("Developer Tools")
});
text: i18n("Open developer tools")
display: QQC2.AbstractButton.IconOnly
Layout.minimumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignRight
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.ToolButton {
icon.name: "settings-configure"
onClicked: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'SettingsPage.qml'), {

View File

@@ -211,6 +211,17 @@ FormCard.FormCardPage {
}
}
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Open proxy settings")
icon.name: "settings-configure"
onClicked: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "NetworkProxyPage.qml"), {}, {
title: i18nc("@title:window", "Proxy Settings")
});
}
}
Component.onCompleted: {
LoginHelper.init();
module.item.forceActiveFocus();

View File

@@ -53,6 +53,9 @@ Kirigami.ApplicationWindow {
MatrixImageProvider.connection = root.connection;
RoomManager.connection = root.connection;
SpaceHierarchyCache.connection = root.connection;
if (ShareHandler.text && root.connection) {
root.handleShare();
}
}
Connections {
@@ -236,6 +239,9 @@ Kirigami.ApplicationWindow {
RoomManager.connection = root.connection;
SpaceHierarchyCache.connection = root.connection;
WindowController.setBlur(pageStack, Config.blur && !Config.compactLayout);
if (ShareHandler.text && root.connection) {
root.handleShare()
}
if (Config.minimizeToSystemTrayOnStartup && !Kirigami.Settings.isMobile && Controller.supportSystemTray && Config.systemTray) {
restoreWindowGeometryConnections.enabled = true; // To restore window size and position
} else {
@@ -425,10 +431,12 @@ Kirigami.ApplicationWindow {
z: 20
x: 0
Accessible.ignored: true
y: parent.height - implicitHeight
contentItem: QQC2.Label {
id: linkText
text: parent.text.startsWith("https://matrix.to/") ? "" : parent.text
Accessible.description: i18nc("@info screenreader", "The currently selected link")
}
Kirigami.Theme.colorSet: Kirigami.Theme.View
background: Rectangle {
@@ -448,4 +456,25 @@ Kirigami.ApplicationWindow {
});
}
}
Connections {
target: ShareHandler
function onTextChanged(): void {
if (root.connection && ShareHandler.text.length > 0) {
handleShare();
}
}
}
function handleShare(): void {
const dialog = applicationWindow().pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ChooseRoomDialog.qml", {
connection: root.connection
}, {
title: i18nc("@title", "Share"),
width: Kirigami.Units.gridUnit * 25
})
dialog.chosen.connect(function(targetRoomId) {
RoomManager.resolveResource(targetRoomId)
ShareHandler.room = targetRoomId
dialog.closeDialog()
})
}
}

37
src/sharehandler.cpp Normal file
View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "sharehandler.h"
ShareHandler::ShareHandler(QObject *parent)
: QObject(parent)
{}
QString ShareHandler::text() const
{
return m_text;
}
void ShareHandler::setText(const QString &text)
{
if (text == m_text) {
return;
}
m_text = text;
Q_EMIT textChanged();
}
QString ShareHandler::room() const
{
return m_room;
}
void ShareHandler::setRoom(const QString &roomId)
{
if (roomId == m_room) {
return;
}
m_room = roomId;
Q_EMIT roomChanged();
}

46
src/sharehandler.h Normal file
View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QObject>
#include <QQmlEngine>
class ShareHandler : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
Q_PROPERTY(QString room READ room WRITE setRoom NOTIFY roomChanged)
public:
static ShareHandler &instance()
{
static ShareHandler _instance;
return _instance;
}
static ShareHandler *create(QQmlEngine *, QJSEngine *)
{
QQmlEngine::setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
QString text() const;
void setText(const QString &url);
QString room() const;
void setRoom(const QString &roomId);
Q_SIGNALS:
void textChanged();
void roomChanged();
private:
explicit ShareHandler(QObject *parent = nullptr);
QString m_text;
QString m_room;
};

View File

@@ -350,8 +350,9 @@ MessageComponent TextHandler::nextBlock(const QString &string,
QString TextHandler::stripBlockTags(QString string, const QString &tagType) const
{
if (blockTags.contains(tagType) && tagType != QStringLiteral("ol") && tagType != QStringLiteral("ul") && tagType != QStringLiteral("table")) {
string.replace(QLatin1String("<%1>").arg(tagType), QString()).replace(QLatin1String("</%1>").arg(tagType), QString());
if (blockTags.contains(tagType) && tagType != QStringLiteral("ol") && tagType != QStringLiteral("ul") && tagType != QStringLiteral("table")
&& string.startsWith(QLatin1String("<%1").arg(tagType))) {
string.remove(0, string.indexOf(u'>') + 1).remove(string.indexOf(QLatin1String("</%1>").arg(tagType)), string.size());
}
if (string.startsWith(QStringLiteral("\n"))) {
@@ -363,7 +364,7 @@ QString TextHandler::stripBlockTags(QString string, const QString &tagType) cons
if (tagType == QStringLiteral("pre")) {
if (string.startsWith(QStringLiteral("<code"))) {
string.remove(0, string.indexOf(u'>') + 1);
string.remove(string.size() - 7, string.size());
string.remove(string.indexOf(QLatin1String("</code>")), string.size());
}
if (string.endsWith(QStringLiteral("\n"))) {
string.remove(string.size() - 1, string.size());
@@ -371,8 +372,8 @@ QString TextHandler::stripBlockTags(QString string, const QString &tagType) cons
}
if (tagType == QStringLiteral("blockquote")) {
if (string.startsWith(QStringLiteral("<p>"))) {
string.remove(0, 3);
string.remove(string.size() - 4, string.size());
string.remove(0, string.indexOf(u'>') + 1);
string.remove(string.indexOf(QLatin1String("</p>")), string.size());
}
if (!string.startsWith(u'"')) {
string.prepend(u'"');
@@ -697,28 +698,7 @@ QString TextHandler::emoteString(const NeoChatRoom *room, const Quotient::RoomEv
QString TextHandler::convertCodeLanguageString(const QString &languageString)
{
const int equalsPos = languageString.indexOf(u'-');
auto data = languageString.right(languageString.length() - equalsPos - 1);
// The standard markdown syntax uses lower case. This will get a subgroup of
// single word languages to work.
if (data.first(1).isLower()) {
data[0] = data[0].toUpper();
}
if (data == QStringLiteral("Cpp")) {
data = QStringLiteral("C++");
}
if (data == QStringLiteral("Json")) {
data = QStringLiteral("JSON");
}
if (data == QStringLiteral("Html")) {
data = QStringLiteral("HTML");
}
if (data == QStringLiteral("Qml")) {
data = QStringLiteral("QML");
}
return data;
return languageString.right(languageString.length() - equalsPos - 1);
}
#include "moc_texthandler.cpp"

View File

@@ -0,0 +1,38 @@
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(timeline STATIC)
qt_add_qml_module(timeline
URI org.kde.neochat.timeline
QML_FILES
EventDelegate.qml
TimelineDelegate.qml
MessageDelegate.qml
LoadingDelegate.qml
ReadMarkerDelegate.qml
StateDelegate.qml
TimelineEndDelegate.qml
Bubble.qml
AvatarFlow.qml
ReactionDelegate.qml
SectionDelegate.qml
MessageComponentChooser.qml
AudioComponent.qml
CodeComponent.qml
EncryptedComponent.qml
FileComponent.qml
ImageComponent.qml
ItineraryComponent.qml
LinkPreviewComponent.qml
LiveLocationComponent.qml
LoadComponent.qml
LocationComponent.qml
MessageEditComponent.qml
MimeComponent.qml
PollComponent.qml
QuoteComponent.qml
ReplyComponent.qml
StateComponent.qml
TextComponent.qml
VideoComponent.qml
)

View File

@@ -108,8 +108,32 @@ QQC2.Control {
}
}
QQC2.Button {
anchors {
top: parent.top
topMargin: Kirigami.Units.smallSpacing
right: parent.right
rightMargin: Kirigami.Units.smallSpacing
}
visible: root.hovered
icon.name: "edit-copy"
text: i18n("Copy to clipboard")
display: QQC2.AbstractButton.IconOnly
onClicked: Clipboard.saveText(root.display);
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
radius: Kirigami.Units.smallSpacing
border {
width: root.hovered ? 1 : 0
color: Kirigami.Theme.highlightColor
}
}
}

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