Compare commits
149 Commits
work/redst
...
v25.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13042d9ba6 | ||
|
|
0e4b52ee62 | ||
|
|
4c32280343 | ||
|
|
a2e540d6ef | ||
|
|
573c8925d2 | ||
|
|
6c0bd850b0 | ||
|
|
1da9719314 | ||
|
|
3cd5f3a1c6 | ||
|
|
b4108f2eef | ||
|
|
bb7de18341 | ||
|
|
7b8328fce6 | ||
|
|
aeee6570c0 | ||
|
|
780b9a6f9b | ||
|
|
0ba06882d1 | ||
|
|
ce131a53e5 | ||
|
|
e9421e28dd | ||
|
|
1c64a6b5f0 | ||
|
|
9b5200c344 | ||
|
|
5baf4ab823 | ||
|
|
58a72a08f2 | ||
|
|
c9b97d4d0d | ||
|
|
2a7d61c73b | ||
|
|
f55bd28e10 | ||
|
|
2a67861099 | ||
|
|
273d962707 | ||
|
|
5e8b44fea6 | ||
|
|
be9e2ec7d0 | ||
|
|
ee042cc1a2 | ||
|
|
25c0bc131a | ||
|
|
7def8c066c | ||
|
|
1ddaf37e52 | ||
|
|
40694f502a | ||
|
|
d9f4a0a032 | ||
|
|
5e392f3101 | ||
|
|
ce1ac6128e | ||
|
|
a9d08a6ee2 | ||
|
|
24d4829ba9 | ||
|
|
a121c39b6e | ||
|
|
f009420c20 | ||
|
|
069e0d8f16 | ||
|
|
1f1db11197 | ||
|
|
be92e56c3a | ||
|
|
d1fc426513 | ||
|
|
c778ba8b24 | ||
|
|
07fb3160eb | ||
|
|
7444d68280 | ||
|
|
1d5536401d | ||
|
|
099e996f2f | ||
|
|
6d9974b2b1 | ||
|
|
f65e9f7599 | ||
|
|
4a39810923 | ||
|
|
6720e2dfaa | ||
|
|
eb1bf2ed2b | ||
|
|
cc26b1b62f | ||
|
|
59c2c88eb6 | ||
|
|
99e8fd16eb | ||
|
|
eaa4bdb0b7 | ||
|
|
892b61e6bb | ||
|
|
6d56b673c4 | ||
|
|
f59e5a4b3c | ||
|
|
16e3fd4476 | ||
|
|
82d0fdefd2 | ||
|
|
cbde14d58b | ||
|
|
f3c37e4304 | ||
|
|
369008cec3 | ||
|
|
1a2af57901 | ||
|
|
dbb41d061c | ||
|
|
d1d3c43c6f | ||
|
|
ba2b1529ea | ||
|
|
b5150f82f0 | ||
|
|
00c81de035 | ||
|
|
52d4451932 | ||
|
|
3efc11b9aa | ||
|
|
5fd9f07664 | ||
|
|
bec9f36bce | ||
|
|
a631acc0ac | ||
|
|
b729aaf6ee | ||
|
|
94b7fc5cdf | ||
|
|
ec5355d86e | ||
|
|
1a43d15c6d | ||
|
|
7356a68f4c | ||
|
|
1070427a0d | ||
|
|
196ef535ca | ||
|
|
88fd173829 | ||
|
|
8b0698c670 | ||
|
|
5b07a0ff45 | ||
|
|
ba1d175d67 | ||
|
|
36ce55e892 | ||
|
|
ba4a83d38c | ||
|
|
5c49c35b82 | ||
|
|
2b0251c593 | ||
|
|
3b08d0f382 | ||
|
|
f64e8a3192 | ||
|
|
eab45e761a | ||
|
|
0f03290c57 | ||
|
|
59f87bb2c2 | ||
|
|
9afc10160d | ||
|
|
cfc7f50a1f | ||
|
|
a2fc00365e | ||
|
|
466cfd971d | ||
|
|
8e83febb24 | ||
|
|
12072b0a73 | ||
|
|
83b1daac07 | ||
|
|
981ea053a6 | ||
|
|
e41fd7d986 | ||
|
|
a88a82ca08 | ||
|
|
23dc88df60 | ||
|
|
520b1daeec | ||
|
|
cbe7ace32d | ||
|
|
2b4471ea91 | ||
|
|
88e1e1dd2a | ||
|
|
94ea1305b2 | ||
|
|
960377838d | ||
|
|
a48e8662d6 | ||
|
|
9c90a38efc | ||
|
|
8696a24127 | ||
|
|
f2d1b4c1e1 | ||
|
|
4cdc2b5e58 | ||
|
|
e26f02d9e2 | ||
|
|
5d83fe9a1d | ||
|
|
6a506f237a | ||
|
|
3a5a0153d8 | ||
|
|
27519b8788 | ||
|
|
dd45fe16a5 | ||
|
|
161815acff | ||
|
|
438edf2155 | ||
|
|
6607a4b72c | ||
|
|
8f0c4ab133 | ||
|
|
78e5cd51cd | ||
|
|
f5da44655e | ||
|
|
a91863c60d | ||
|
|
29e3a09aba | ||
|
|
a59952f189 | ||
|
|
b169da25ab | ||
|
|
833e357d70 | ||
|
|
f9bf2b8f7a | ||
|
|
7b27579f2d | ||
|
|
edb9d9f54e | ||
|
|
abf9a486d0 | ||
|
|
b0bc19c055 | ||
|
|
71776ef275 | ||
|
|
8dc7d1d39d | ||
|
|
adb9e59503 | ||
|
|
518044e34a | ||
|
|
d6d747bb99 | ||
|
|
f390702a7a | ||
|
|
e056360ddd | ||
|
|
85163791ce | ||
|
|
b5a853bc96 |
@@ -4,5 +4,3 @@
|
||||
[BlueprintSettings]
|
||||
kde/applications/neochat.packageAppx=True
|
||||
libs/qt.qtMajorVersion=6
|
||||
; Remove once KNotifications v6.19 releases, to make Android notifications work again
|
||||
kde/frameworks/tier3/knotifications.version=master
|
||||
|
||||
@@ -14,7 +14,6 @@ Dependencies:
|
||||
'frameworks/kquickcharts': '@latest-kf6'
|
||||
'frameworks/knotifications': '@latest-kf6'
|
||||
'frameworks/kcolorscheme': '@latest-kf6'
|
||||
'frameworks/kiconthemes': '@latest-kf6'
|
||||
'libraries/kquickimageeditor': '@latest-kf6'
|
||||
'frameworks/sonnet': '@latest-kf6'
|
||||
'frameworks/prison': '@latest-kf6'
|
||||
@@ -29,7 +28,6 @@ Dependencies:
|
||||
'frameworks/kio': '@latest-kf6'
|
||||
'frameworks/kwindowsystem': '@latest-kf6'
|
||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||
'frameworks/kcrash': '@latest-kf6'
|
||||
- 'on': ['Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@latest-kf6'
|
||||
|
||||
@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "25")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "12")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "0")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
@@ -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 6.0)
|
||||
set(KDE_COMPILERSETTINGS_LEVEL 6.17)
|
||||
|
||||
include(FeatureSummary)
|
||||
include(ECMSetupVersion)
|
||||
@@ -39,6 +39,7 @@ include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
include(ECMAddAndroidApk)
|
||||
include(ECMQmlModule)
|
||||
include(ECMDeprecationSettings)
|
||||
include(GenerateExportHeader)
|
||||
include(ECMGenerateHeaders)
|
||||
if (NOT ANDROID)
|
||||
@@ -51,6 +52,8 @@ endif()
|
||||
|
||||
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
|
||||
|
||||
ecm_set_disabled_deprecation_versions(Qt 6.9.0 KF 6.17.0)
|
||||
|
||||
ecm_setup_version(${PROJECT_VERSION}
|
||||
VARIABLE_PREFIX NEOCHAT
|
||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||
@@ -66,7 +69,7 @@ if (QT_KNOWN_POLICY_QTP0004)
|
||||
qt_policy(SET QTP0004 NEW)
|
||||
endif ()
|
||||
|
||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels IconThemes ColorScheme)
|
||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
|
||||
set_package_properties(KF6 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
@@ -75,7 +78,7 @@ set_package_properties(KF6Kirigami PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Kirigami application UI framework"
|
||||
)
|
||||
find_package(KF6KirigamiAddons 1.6.0 REQUIRED)
|
||||
find_package(KF6KirigamiAddons 1.10.0 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)
|
||||
@@ -89,7 +92,7 @@ if(ANDROID)
|
||||
)
|
||||
else()
|
||||
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem Crash)
|
||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem)
|
||||
find_package(KF6SyntaxHighlighting ${KF_MIN_VERSION} REQUIRED)
|
||||
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
|
||||
TYPE RUNTIME
|
||||
|
||||
@@ -4,15 +4,12 @@
|
||||
#include "server.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QHttpServer>
|
||||
#include <QHttpServerResponder>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QSslServer>
|
||||
#include <QUuid>
|
||||
|
||||
#include <Quotient/networkaccessmanager.h>
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<name xml:lang="pl">NeoChat</name>
|
||||
<name xml:lang="pt">NeoChat</name>
|
||||
<name xml:lang="pt-BR">NeoChat</name>
|
||||
<name xml:lang="ro">NeoChat</name>
|
||||
<name xml:lang="ru">NeoChat</name>
|
||||
<name xml:lang="sa">नवचैट्</name>
|
||||
<name xml:lang="sk">NeoChat</name>
|
||||
@@ -75,6 +76,7 @@
|
||||
<summary xml:lang="nn">Prat med via Matrix</summary>
|
||||
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
|
||||
<summary xml:lang="pt-BR">Bate-papo na Matrix</summary>
|
||||
<summary xml:lang="ro">Discutați pe Matrix</summary>
|
||||
<summary xml:lang="ru">Общение в Matrix</summary>
|
||||
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
|
||||
<summary xml:lang="sl">Klepet na Matrixu</summary>
|
||||
@@ -109,6 +111,7 @@
|
||||
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
|
||||
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat é um aplicativo de bate-papo que permite que você aproveite ao máximo a rede Matrix. Ele oferece uma maneira segura de enviar mensagens de texto, vídeos e arquivos de áudio para sua família, colegas e amigos.</p>
|
||||
<p xml:lang="ro">NeoChat e o aplicație de discuții ce vă ajută să profitați din plin de rețeaua Matrix. Aceasta oferă o modalitate sigură de a trimite mesaje textuale, videoclipuri și fișiere audio familiei, colegilor și prietenilor.</p>
|
||||
<p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p>
|
||||
<p xml:lang="sa">NeoChat इति एकं गपशप-अनुप्रयोगं यत् भवान् Matrix-जालस्य पूर्णं लाभं ग्रहीतुं शक्नोति । एतत् भवन्तं भवतः परिवाराय, सहकारिभ्यः, मित्रेभ्यः च पाठसन्देशान्, भिडियो, श्रव्यसञ्चिकाः च प्रेषयितुं सुरक्षितं मार्गं प्रदाति ।</p>
|
||||
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
|
||||
@@ -142,6 +145,7 @@
|
||||
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
|
||||
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat pretende ser um aplicativo completo para a especificação Matrix. Dessa forma, tudo na especificação estável atual, com as notáveis exceções de VoIP, tópicos e alguns aspectos da criptografia de ponta a ponta, é suportado. Há algumas outras pequenas omissões devido ao fato de a especificação Matrix estar em constante evolução, mas o objetivo continua sendo fornecer suporte eventual para toda a especificação.</p>
|
||||
<p xml:lang="ro">NeoChat vrea să fie o aplicație completă pentru specificațiile Matrix. Astfel, susține tot ce se găsește acum în specificațiile stabile cu excepția VoIP, a firelor de discuții, și a unor părți din criptarea punct-la-punct. Sunt și câteva omisiuni minore din cauza faptului că specificația Matrix evoluează continuu, dar scopul rămâne acela de a implementa întreaga specificație.</p>
|
||||
<p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p>
|
||||
<p xml:lang="sa">NeoChat इत्यस्य उद्देश्यं Matrix विनिर्देशस्य कृते पूर्णतया विशेषतायुक्तः अनुप्रयोगः भवितुम् अस्ति । यथा तथा वर्तमानस्थिरविनिर्देशे सर्वं VoIP इत्यस्य उल्लेखनीयअपवादैः सह, थ्रेड्स तथा च End-to-End Encryption इत्यस्य केचन पक्षाः समर्थिताः सन्ति । अन्ये कतिचन लघु लोपाः सन्ति यतोहि Matrix spec निरन्तरं विकसितः अस्ति परन्तु उद्देश्यं सम्पूर्ण spec कृते अन्ततः समर्थनं प्रदातुं अवशिष्टम् अस्ति</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>
|
||||
@@ -175,6 +179,7 @@
|
||||
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
|
||||
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
|
||||
<p xml:lang="pt-BR">Devido à natureza do desenvolvimento da especificação Matrix, o NeoChat também suporta diversos recursos instáveis. Atualmente, são eles:</p>
|
||||
<p xml:lang="ro">Datorită modului de dezvoltare a specificațiilor Matrix, NeoChat susține și numeroase caracteristici nestabile. Acum, acestea sunt:</p>
|
||||
<p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p>
|
||||
<p xml:lang="sa">Matrix विनिर्देशविकासस्य प्रकृतेः कारणात् NeoChat अपि अनेकानाम् अस्थिरविशेषतानां समर्थनं करोति । सम्प्रति एते सन्ति :</p>
|
||||
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
|
||||
@@ -209,6 +214,7 @@
|
||||
<li xml:lang="pl">Ankiety - MSC3381</li>
|
||||
<li xml:lang="pt">Inquéritos - MSC3381</li>
|
||||
<li xml:lang="pt-BR">Enquetes - MSC3381</li>
|
||||
<li xml:lang="ro">Sondaje - MSC3381</li>
|
||||
<li xml:lang="ru">Голосования — MSC3381</li>
|
||||
<li xml:lang="sa">मतदान - MSC3381</li>
|
||||
<li xml:lang="sl">Polls - MSC3381</li>
|
||||
@@ -242,6 +248,7 @@
|
||||
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
|
||||
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
|
||||
<li xml:lang="pt-BR">Pacotes de Stickers - MSC2545</li>
|
||||
<li xml:lang="ro">Colecții de abțibilduri - MSC2545</li>
|
||||
<li xml:lang="ru">Наборы стикеров — MSC2545</li>
|
||||
<li xml:lang="sa">स्टिकर पैक - MSC2545</li>
|
||||
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
||||
@@ -275,6 +282,7 @@
|
||||
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
|
||||
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
|
||||
<li xml:lang="pt-BR">Localização de eventos - MSC3488</li>
|
||||
<li xml:lang="ro">Evenimente de amplasare - MSC3488</li>
|
||||
<li xml:lang="ru">События местоположения — MSC3488</li>
|
||||
<li xml:lang="sa">स्थान घटनाएँ - MSC3488</li>
|
||||
<li xml:lang="sl">Location Events - MSC3488</li>
|
||||
@@ -312,7 +320,7 @@
|
||||
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
|
||||
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
|
||||
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
|
||||
<value key="KDE::supporters">Tanguy Fardet;[dabe](https://freeradical.zone/@dabe);[lengau](https://mastodon.world/@lengau);Joshua Strobl;Stuart Turton</value>
|
||||
<value key="KDE::supporters">Anonymous donor, Akseli</value>
|
||||
</custom>
|
||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||
<screenshots>
|
||||
@@ -344,6 +352,7 @@
|
||||
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
|
||||
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
|
||||
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
|
||||
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
|
||||
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
|
||||
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
|
||||
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
|
||||
@@ -380,6 +389,7 @@
|
||||
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
|
||||
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
|
||||
<caption xml:lang="pt-BR">Descubra novas comunidades com os Espaços Matrix</caption>
|
||||
<caption xml:lang="ro">Descoperiți comunități noi cu Spații Matrix</caption>
|
||||
<caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption>
|
||||
<caption xml:lang="sa">Matrix Spaces इत्यनेन सह नूतनानां समुदायानाम् अन्वेषणं कुर्वन्तु</caption>
|
||||
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
|
||||
@@ -424,6 +434,7 @@
|
||||
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
|
||||
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
|
||||
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
|
||||
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
|
||||
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
|
||||
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
|
||||
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
|
||||
@@ -462,6 +473,7 @@
|
||||
<caption xml:lang="pl">Ekran logowania</caption>
|
||||
<caption xml:lang="pt">Ecrã de autenticação</caption>
|
||||
<caption xml:lang="pt-BR">Tela de login</caption>
|
||||
<caption xml:lang="ro">Ecran de autentificare</caption>
|
||||
<caption xml:lang="ru">Окно входа</caption>
|
||||
<caption xml:lang="sa">लॉगिन् स्क्रीन</caption>
|
||||
<caption xml:lang="sl">Prijavni zaslon</caption>
|
||||
@@ -476,6 +488,9 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="25.12.0" date="2025-12-11"/>
|
||||
<release version="25.08.3" date="2025-11-06"/>
|
||||
<release version="25.08.2" date="2025-10-09"/>
|
||||
<release version="25.08.1" date="2025-09-11"/>
|
||||
<release version="25.08.0" date="2025-08-14"/>
|
||||
<release version="25.04.3" date="2025-07-03"/>
|
||||
|
||||
@@ -112,6 +112,7 @@ Comment[lv]=Tērzējiet „Matrix“ tīklā
|
||||
Comment[nl]=Chat op Matrix
|
||||
Comment[pl]=Rozmawiaj na Matriksie
|
||||
Comment[pt_BR]=Bate papo na Matrix
|
||||
Comment[ro]=Discutați pe Matrix
|
||||
Comment[ru]=Общение в Matrix
|
||||
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
|
||||
Comment[sl]=Klepet na Matrixu
|
||||
|
||||
1383
po/ar/neochat.po
1383
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1185
po/ast/neochat.po
1185
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
1350
po/az/neochat.po
1350
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1311
po/ca/neochat.po
1311
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1260
po/cs/neochat.po
1260
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1295
po/da/neochat.po
1295
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1439
po/de/neochat.po
1439
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1399
po/el/neochat.po
1399
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1424
po/en_GB/neochat.po
1424
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1415
po/eo/neochat.po
1415
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1234
po/es/neochat.po
1234
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1424
po/eu/neochat.po
1424
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1884
po/fi/neochat.po
1884
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1371
po/fr/neochat.po
1371
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1428
po/gl/neochat.po
1428
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
1325
po/he/neochat.po
1325
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
1407
po/hi/neochat.po
1407
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
1728
po/hu/neochat.po
1728
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1438
po/ia/neochat.po
1438
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1391
po/id/neochat.po
1391
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1330
po/ie/neochat.po
1330
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1338
po/it/neochat.po
1338
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1176
po/ja/neochat.po
1176
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1324
po/ka/neochat.po
1324
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1881
po/ko/neochat.po
1881
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
2064
po/lt/neochat.po
2064
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1721
po/lv/neochat.po
1721
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1333
po/nl/neochat.po
1333
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1281
po/nn/neochat.po
1281
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1347
po/pa/neochat.po
1347
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
2340
po/pl/neochat.po
2340
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1380
po/pt/neochat.po
1380
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1388
po/pt_BR/neochat.po
1388
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
7049
po/ro/neochat.po
Normal file
7049
po/ro/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1436
po/ru/neochat.po
1436
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1407
po/sa/neochat.po
1407
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
1381
po/sk/neochat.po
1381
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1323
po/sl/neochat.po
1323
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1330
po/sv/neochat.po
1330
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1906
po/ta/neochat.po
1906
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1305
po/tok/neochat.po
1305
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1355
po/tr/neochat.po
1355
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1345
po/uk/neochat.po
1345
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1416
po/zh_CN/neochat.po
1416
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1746
po/zh_TW/neochat.po
1746
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -103,6 +103,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/ReasonDialog.qml
|
||||
qml/NewPollDialog.qml
|
||||
qml/UserMenu.qml
|
||||
qml/MeetingDialog.qml
|
||||
DEPENDENCIES
|
||||
QtCore
|
||||
QtQuick
|
||||
@@ -202,8 +203,9 @@ target_link_libraries(neochat PUBLIC
|
||||
KF6::ConfigGui
|
||||
KF6::CoreAddons
|
||||
KF6::SonnetCore
|
||||
KF6::IconThemes
|
||||
KF6::ItemModels
|
||||
KF6::I18nQml
|
||||
KirigamiApp
|
||||
QuotientQt6
|
||||
Login
|
||||
Rooms
|
||||
@@ -211,10 +213,6 @@ target_link_libraries(neochat PUBLIC
|
||||
Spaces
|
||||
)
|
||||
|
||||
if (TARGET KF6::Crash)
|
||||
target_link_libraries(neochat PUBLIC KF6::Crash)
|
||||
endif()
|
||||
|
||||
kconfig_target_kcfg_file(neochat FILE neochatconfig.kcfg CLASS_NAME NeoChatConfig MUTATORS GENERATE_PROPERTIES DEFAULT_VALUE_GETTERS PARENT_IN_CONSTRUCTOR SINGLETON GENERATE_MOC QML_REGISTRATION)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
@@ -325,6 +323,7 @@ if(ANDROID)
|
||||
"kt-restore-defaults-symbolic"
|
||||
"user-symbolic"
|
||||
"mark-location-symbolic"
|
||||
"amarok_playcount"
|
||||
|
||||
${KIRIGAMI_ADDONS_ICONS}
|
||||
)
|
||||
@@ -353,7 +352,8 @@ endif()
|
||||
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
|
||||
# krunner plugin must be the same as the app id for flatpak to export it
|
||||
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins RENAME org.kde.neochat.desktop)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
#include <Quotient/settings.h>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "enums/roomsortparameter.h"
|
||||
#include "general_logging.h"
|
||||
#include "mediasizehelper.h"
|
||||
@@ -26,9 +25,7 @@
|
||||
#include "models/roomlistmodel.h"
|
||||
#include "models/roomtreemodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "proxycontroller.h"
|
||||
#include "roommanager.h"
|
||||
|
||||
@@ -309,8 +306,7 @@ void Controller::listenForNotifications()
|
||||
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
|
||||
|
||||
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
|
||||
instance().m_notificationsManager.postPushNotification(data);
|
||||
timer->stop();
|
||||
NotificationsManager::postPushNotification(data);
|
||||
});
|
||||
|
||||
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
|
||||
|
||||
@@ -33,13 +33,9 @@
|
||||
#include <KWindowSystem>
|
||||
#endif
|
||||
|
||||
#if __has_include("KCrash")
|
||||
#include <KCrash>
|
||||
#endif
|
||||
|
||||
#include <KIconTheme>
|
||||
#include <KLocalizedContext>
|
||||
#include <KLocalizedQmlContext>
|
||||
#include <KLocalizedString>
|
||||
#include <KirigamiApp>
|
||||
|
||||
#include "neochat-version.h"
|
||||
|
||||
@@ -104,7 +100,6 @@ Q_DECL_EXPORT
|
||||
#endif
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
KIconTheme::initTheme();
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
|
||||
#ifdef HAVE_WEBVIEW
|
||||
@@ -113,24 +108,10 @@ int main(int argc, char *argv[])
|
||||
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
QGuiApplication app(argc, argv);
|
||||
QQuickStyle::setStyle(u"org.kde.breeze"_s);
|
||||
#else
|
||||
QIcon::setFallbackThemeName("breeze"_L1);
|
||||
QApplication app(argc, argv);
|
||||
// Default to org.kde.desktop style unless the user forces another style
|
||||
if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
|
||||
QQuickStyle::setStyle(u"org.kde.desktop"_s);
|
||||
}
|
||||
#endif
|
||||
KirigamiApp::App app(argc, argv);
|
||||
KirigamiApp kirigamiApp;
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
freopen("CONOUT$", "w", stderr);
|
||||
}
|
||||
|
||||
QApplication::setStyle(u"breeze"_s);
|
||||
QFont font(u"Segoe UI Emoji"_s);
|
||||
font.setPointSize(10);
|
||||
@@ -177,10 +158,6 @@ int main(int argc, char *argv[])
|
||||
KAboutData::setApplicationData(about);
|
||||
QGuiApplication::setWindowIcon(QIcon::fromTheme(u"org.kde.neochat"_s));
|
||||
|
||||
#if __has_include("KCrash")
|
||||
KCrash::initialize();
|
||||
#endif
|
||||
|
||||
Connection::setEncryptionDefault(true);
|
||||
Connection::setDirectChatEncryptionDefault(true);
|
||||
|
||||
@@ -205,7 +182,7 @@ int main(int argc, char *argv[])
|
||||
parser.addOption(testOption);
|
||||
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s, i18n("Internal usage only."));
|
||||
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s);
|
||||
dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
|
||||
parser.addOption(dbusActivatedOption);
|
||||
#endif
|
||||
@@ -219,8 +196,14 @@ int main(int argc, char *argv[])
|
||||
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
if (parser.isSet(dbusActivatedOption)) {
|
||||
// We want to be replaceable by the main client
|
||||
KDBusService service(KDBusService::Replace);
|
||||
#ifdef HAVE_KDBUSADDONS
|
||||
// We *don't* want to use KDBusService here. I don't know why, but it makes activation super unreliable. We don't really need it anyway.
|
||||
if (!QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.neochat"))) {
|
||||
// Gracefully fail if NeoChat is already running
|
||||
qWarning() << "NeoChat already running, not sending push notifications.";
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
// If we are built with KRunner and KUnifiedPush support, we need to do something special.
|
||||
@@ -279,7 +262,7 @@ int main(int argc, char *argv[])
|
||||
});
|
||||
#endif
|
||||
|
||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||
KLocalization::setupLocalizedContext(&engine);
|
||||
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
|
||||
|
||||
if (parser.isSet("ignore-ssl-errors"_L1)) {
|
||||
@@ -294,7 +277,9 @@ int main(int argc, char *argv[])
|
||||
|
||||
engine.addImageProvider(u"blurhash"_s, new BlurhashImageProvider);
|
||||
|
||||
engine.loadFromModule("org.kde.neochat", "Main");
|
||||
if (!kirigamiApp.start("org.kde.neochat", "Main", &engine)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!parser.positionalArguments().isEmpty() && !parser.isSet("share"_L1)) {
|
||||
RoomManager::instance().setUrlArgument(parser.positionalArguments()[0]);
|
||||
|
||||
@@ -211,6 +211,7 @@ Name[pa]=ਨਵਾਂ ਸੱਦਾ
|
||||
Name[pl]=Nowe zaproszenie
|
||||
Name[pt]=Novo Convite
|
||||
Name[pt_BR]=Novo convite
|
||||
Name[ro]=Invitație nouă
|
||||
Name[ru]=Новое приглашение
|
||||
Name[sa]=नवीन आमन्त्रणम्
|
||||
Name[sl]=Novo povabilo
|
||||
@@ -252,12 +253,13 @@ Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
|
||||
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
|
||||
Comment[pt]=Existe um novo convite para uma sala
|
||||
Comment[pt_BR]=Existe um novo convite para uma sala
|
||||
Comment[ro]=E o nouă invitație la o cameră
|
||||
Comment[ru]=Доступно новое приглашение в комнату
|
||||
Comment[sa]=कक्षस्य नूतनं निमन्त्रणम् अस्ति
|
||||
Comment[sl]=Tam je novo povabilo v sobo
|
||||
Comment[sv]=Det finns en ny inbjudan till ett rum
|
||||
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
|
||||
Comment[tr]=Bir odaya yeni bir davetiye var
|
||||
Comment[tr]=Bir odaya yeni bir davet var
|
||||
Comment[uk]=У кімнаті нове запрошення
|
||||
Comment[zh_CN]=有新的聊天室邀请
|
||||
Comment[zh_TW]=有新的加入聊天室邀請
|
||||
@@ -290,6 +292,7 @@ Name[nl]=Gedeelde
|
||||
Name[nn]=Del
|
||||
Name[pl]=Udostępnij
|
||||
Name[pt_BR]=Compartilhar
|
||||
Name[ro]=Partajare
|
||||
Name[ru]=Публикация
|
||||
Name[sa]=संविभागः
|
||||
Name[sl]=Deli
|
||||
@@ -324,6 +327,7 @@ Comment[nl]=Het resultaat van het delen van een stukje inhoud
|
||||
Comment[nn]=Resultatet av deling av innhald
|
||||
Comment[pl]=Wynik udostępniania kawałka treści
|
||||
Comment[pt_BR]=O resultado de compartilhar um conteúdo
|
||||
Comment[ro]=Rezultatul partajării unei bucăți de conținut
|
||||
Comment[ru]=Результат публикации данных
|
||||
Comment[sa]=सामग्रीखण्डस्य साझाकरणस्य परिणामः
|
||||
Comment[sl]=Rezultat deljenega kosa vsebine
|
||||
|
||||
@@ -66,6 +66,10 @@
|
||||
</entry>
|
||||
</group>
|
||||
<group name="Timeline">
|
||||
<entry name="FontScale" type="double">
|
||||
<label>Scaling factor for font sizes</label>
|
||||
<default>1.0</default>
|
||||
</entry>
|
||||
<entry name="ShowAvatarInTimeline" type="bool">
|
||||
<label>Show avatar in the timeline</label>
|
||||
<default>true</default>
|
||||
|
||||
@@ -388,7 +388,7 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
|
||||
|
||||
#ifdef HAVE_KIO
|
||||
auto openAction = notification->addAction(i18n("Open NeoChat"));
|
||||
connect(openAction, &KNotificationAction::activated, this, [=]() {
|
||||
connect(openAction, &KNotificationAction::activated, notification, [=]() {
|
||||
QString properId = roomId;
|
||||
properId = properId.replace(u"#"_s, QString());
|
||||
properId = properId.replace(u"!"_s, QString());
|
||||
@@ -402,8 +402,6 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
|
||||
connect(notification, &KNotification::closed, qGuiApp, &QGuiApplication::quit);
|
||||
|
||||
notification->sendEvent();
|
||||
|
||||
m_notifications.insert(roomId, {json["ts"_L1].toVariant().toLongLong(), notification});
|
||||
} else {
|
||||
qWarning() << "Skipping unsupported push notification" << type;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
/**
|
||||
* @brief Display a native notification for the given push notification.
|
||||
*/
|
||||
void postPushNotification(const QByteArray &message);
|
||||
static void postPushNotification(const QByteArray &message);
|
||||
|
||||
/**
|
||||
* @brief Handle the notifications for the given connection.
|
||||
|
||||
@@ -75,6 +75,7 @@ Comment[nn]=Finn rom i NeoChat
|
||||
Comment[pl]=Znajdź pokoje w NeoChat
|
||||
Comment[pt]=Procurar salas no NeoChat
|
||||
Comment[pt_BR]=Encontrar salas no NeoChat
|
||||
Comment[ro]=Găsește camere în NeoChat
|
||||
Comment[ru]=Поиск комнат NeoChat
|
||||
Comment[sa]=NeoChat इत्यत्र कक्ष्याः अन्वेषणं कुर्वन्तु
|
||||
Comment[sl]=Najdi sobe v NeoChatu
|
||||
|
||||
@@ -61,7 +61,7 @@ Kirigami.Dialog {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {}, {
|
||||
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {}, {
|
||||
title: i18nc("@title:window", "Login")
|
||||
});
|
||||
root.close();
|
||||
@@ -95,8 +95,8 @@ Kirigami.Dialog {
|
||||
accountView.decrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
Keys.onEnterPressed: (accountView.currentItem as Delegates.RoundedItemDelegate ?? accountView.footerItem).clicked()
|
||||
Keys.onReturnPressed: (accountView.currentItem as Delegates.RoundedItemDelegate ?? accountView.footerItem).clicked()
|
||||
Keys.onEnterPressed: ((accountView.currentItem ?? accountView.footerItem) as Delegates.RoundedItemDelegate).clicked()
|
||||
Keys.onReturnPressed: ((accountView.currentItem ?? accountView.footerItem) as Delegates.RoundedItemDelegate).clicked()
|
||||
|
||||
onVisibleChanged: {
|
||||
for (let i = 0; i < accountView.count; i++) {
|
||||
|
||||
@@ -91,6 +91,7 @@ Components.AbstractMaximizeComponent {
|
||||
color: Kirigami.Theme.textColor
|
||||
|
||||
font.family: "monospace"
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * NeoChatConfig.fontScale
|
||||
|
||||
Kirigami.SpellCheck.enabled: false
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ ApplicationWindow {
|
||||
property real longitude: NaN
|
||||
property string asset
|
||||
property var author
|
||||
property QtObject liveLocationModel: null
|
||||
property LiveLocationsModel liveLocationModel: null
|
||||
|
||||
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
||||
visibility: Qt.WindowFullScreen
|
||||
@@ -59,7 +59,7 @@ ApplicationWindow {
|
||||
|
||||
Connections {
|
||||
target: mapView.map
|
||||
function onCopyrightLinkActivated() {
|
||||
function onCopyrightLinkActivated(link: string) {
|
||||
Qt.openUrlExternally(link);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ Labs.MenuBar {
|
||||
|
||||
Labs.MenuItem {
|
||||
icon.name: "list-add-user"
|
||||
text: i18nc("@action:inmenu", "Find your Friends")
|
||||
text: i18nc("@action:inmenu", "Find User")
|
||||
enabled: root.connection
|
||||
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Find your friends")
|
||||
title: i18nc("@title", "Find User")
|
||||
})
|
||||
}
|
||||
Labs.MenuItem {
|
||||
|
||||
@@ -100,7 +100,8 @@ Kirigami.ApplicationWindow {
|
||||
|
||||
function onCurrentRoomChanged() {
|
||||
if (RoomManager.currentRoom && root.pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
|
||||
let roomPage = root.pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
|
||||
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
|
||||
roomPage.forceActiveFocus();
|
||||
roomPage.backRequested.connect(event => {
|
||||
RoomManager.clearCurrentRoom();
|
||||
});
|
||||
|
||||
22
src/app/qml/MeetingDialog.qml
Normal file
22
src/app/qml/MeetingDialog.qml
Normal file
@@ -0,0 +1,22 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
Kirigami.PromptDialog {
|
||||
id: root
|
||||
|
||||
required property bool hasExistingMeeting
|
||||
|
||||
title: hasExistingMeeting ? i18nc("@title", "Join Meeting") : i18nc("@title", "Start Meeting")
|
||||
subtitle: hasExistingMeeting ? i18nc("@info:label", "You are about to join a Jitsi meeting in your web browser.") : i18nc("@info:label", "You are about to start a new Jitsi meeting in your web browser.")
|
||||
standardButtons: Kirigami.Dialog.Cancel
|
||||
|
||||
customFooterActions: Kirigami.Action {
|
||||
icon.name: "camera-video-symbolic"
|
||||
text: hasExistingMeeting ? i18nc("@action:button Join the Jitsi meeting", "Join") : i18nc("@action:button Start a new Jitsi meeting", "Start")
|
||||
onTriggered: root.accept()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// 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
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore as Core
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
@@ -21,13 +23,13 @@ Components.AlbumMaximizeComponent {
|
||||
*/
|
||||
required property NeoChatRoom currentRoom
|
||||
|
||||
readonly property string currentEventId: model.data(model.index(content.currentIndex, 0), TimelineMessageModel.EventIdRole)
|
||||
readonly property string currentEventId: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.EventIdRole)
|
||||
|
||||
readonly property var currentAuthor: model.data(model.index(content.currentIndex, 0), TimelineMessageModel.AuthorRole)
|
||||
readonly property var currentAuthor: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.AuthorRole)
|
||||
|
||||
readonly property var currentTime: model.data(model.index(content.currentIndex, 0), TimelineMessageModel.TimeRole)
|
||||
readonly property var currentTime: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.TimeRole)
|
||||
|
||||
readonly property var currentProgressInfo: model.data(model.index(content.currentIndex, 0), TimelineMessageModel.ProgressInfoRole)
|
||||
readonly property var currentProgressInfo: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.ProgressInfoRole)
|
||||
|
||||
actions: [
|
||||
ShareAction {
|
||||
@@ -59,28 +61,28 @@ Components.AlbumMaximizeComponent {
|
||||
|
||||
downloadAction: Components.DownloadAction {
|
||||
onTriggered: {
|
||||
currentRoom.downloadFile(root.currentEventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.currentEventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(root.currentEventId));
|
||||
root.currentRoom.downloadFile(root.currentEventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.currentEventId.replace(":", "_").replace("/", "_").replace("+", "_") + root.currentRoom.fileNameToDownload(root.currentEventId));
|
||||
}
|
||||
}
|
||||
|
||||
playAction: Kirigami.Action {
|
||||
onTriggered: {
|
||||
MediaManager.startPlayback();
|
||||
currentItem.play();
|
||||
(root.currentItem as Components.VideoMaximizeDelegate).play();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: MediaManager
|
||||
function onPlaybackStarted() {
|
||||
if (currentItem.playbackState === MediaPlayer.PlayingState) {
|
||||
currentItem.pause();
|
||||
if ((root.currentItem as Components.VideoMaximizeDelegate).playbackState === MediaPlayer.PlayingState) {
|
||||
(root.currentItem as Components.VideoMaximizeDelegate).pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: currentRoom
|
||||
target: root.currentRoom
|
||||
|
||||
function onFileTransferProgress(id, progress, total) {
|
||||
if (id == root.currentEventId) {
|
||||
@@ -123,7 +125,7 @@ Components.AlbumMaximizeComponent {
|
||||
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom)
|
||||
|
||||
onSaveItem: {
|
||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
|
||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay) as Dialogs.FileDialog;
|
||||
dialog.selectedFile = currentRoom.fileNameToDownload(root.currentEventId);
|
||||
dialog.open();
|
||||
}
|
||||
@@ -146,7 +148,7 @@ Components.AlbumMaximizeComponent {
|
||||
if (!selectedFile) {
|
||||
return;
|
||||
}
|
||||
currentRoom.downloadFile(root.currentEventId, selectedFile);
|
||||
root.currentRoom.downloadFile(root.currentEventId, selectedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +59,59 @@ Kirigami.Page {
|
||||
*/
|
||||
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
|
||||
|
||||
/**
|
||||
* @brief The WidgetModel to use.
|
||||
*
|
||||
* This model has the list of widgets available in the current room.
|
||||
*
|
||||
* @note For loading a room in a different window, override this with a new
|
||||
* WidgetModel.
|
||||
*
|
||||
* @sa WidgetModel
|
||||
*/
|
||||
property WidgetModel widgetModel: RoomManager.widgetModel
|
||||
|
||||
title: root.currentRoom ? root.currentRoom.displayName : ""
|
||||
focus: true
|
||||
padding: 0
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
id: jitsiMeetingAction
|
||||
|
||||
readonly property bool hasExistingMeeting: root.widgetModel.jitsiIndex >= 0
|
||||
readonly property bool canStartNewMeeting: root.currentRoom.canSendState("im.vector.modular.widgets")
|
||||
|
||||
tooltip: {
|
||||
if (hasExistingMeeting) {
|
||||
return i18nc("@action:button", "Join Jitsi meeting…");
|
||||
}
|
||||
|
||||
return canStartNewMeeting ? i18nc("@action:button", "Start Jitsi meeting…") : i18nc("@action:button", "You do not have permissions to start Jitsi meetings")
|
||||
}
|
||||
icon {
|
||||
name: "camera-video-symbolic"
|
||||
color: hasExistingMeeting ? Kirigami.Theme.highlightColor : "transparent"
|
||||
}
|
||||
enabled: hasExistingMeeting || canStartNewMeeting
|
||||
visible: root.currentRoom && !root.currentRoom.isSpace
|
||||
onTriggered: {
|
||||
const dialog = Qt.createComponent("org.kde.neochat", "MeetingDialog").createObject(QQC2.Overlay.overlay, { hasExistingMeeting });
|
||||
dialog.onAccepted.connect(doAction);
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
function doAction(): void {
|
||||
let url;
|
||||
if (!hasExistingMeeting) {
|
||||
url = root.widgetModel.addJitsiConference();
|
||||
} else {
|
||||
let idx = root.widgetModel.index(root.widgetModel.jitsiIndex, 0);
|
||||
url = root.widgetModel.data(idx, WidgetModel.UrlRole);
|
||||
}
|
||||
Qt.openUrlExternally(url);
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
visible: Kirigami.Settings.isMobile || !(root.Kirigami.PageStack.pageStack as Kirigami.PageRow).wideMode
|
||||
icon.name: "view-right-new"
|
||||
@@ -71,6 +119,18 @@ Kirigami.Page {
|
||||
}
|
||||
]
|
||||
|
||||
Kirigami.Action {
|
||||
enabled: root.currentRoom && !root.currentRoom.isSpace
|
||||
shortcut: "Ctrl+F"
|
||||
onTriggered: {
|
||||
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
|
||||
room: root.currentRoom
|
||||
}, {
|
||||
title: i18nc("@action:title", "Search")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.left: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).get(0)
|
||||
|
||||
onCurrentRoomChanged: {
|
||||
@@ -226,12 +286,6 @@ Kirigami.Page {
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
color: NeoChatConfig.compactLayout ? Kirigami.Theme.backgroundColor : "transparent"
|
||||
}
|
||||
|
||||
footer: Loader {
|
||||
id: chatBarLoader
|
||||
height: active ? (item as ChatBar).implicitHeight : 0
|
||||
|
||||
@@ -30,7 +30,7 @@ SearchPage {
|
||||
*/
|
||||
required property NeoChatConnection connection
|
||||
|
||||
title: i18nc("@action:title", "Find Your Friends")
|
||||
title: i18nc("@action:title", "Find User")
|
||||
|
||||
Component.onCompleted: focusSearch()
|
||||
|
||||
@@ -81,7 +81,7 @@ SearchPage {
|
||||
}
|
||||
QQC2.Label {
|
||||
visible: userDelegate.directChatExists
|
||||
text: i18nc("@info", "Friends")
|
||||
text: i18nc("@info", "Direct Messages")
|
||||
textFormat: Text.PlainText
|
||||
color: Kirigami.Theme.positiveTextColor
|
||||
}
|
||||
|
||||
@@ -11,7 +11,19 @@ VerificationMessage {
|
||||
|
||||
required property int reason
|
||||
|
||||
icon: "security-low"
|
||||
icon: {
|
||||
switch (root.reason) {
|
||||
case KeyVerificationSession.TIMEOUT:
|
||||
case KeyVerificationSession.REMOTE_TIMEOUT:
|
||||
case KeyVerificationSession.USER:
|
||||
case KeyVerificationSession.REMOTE_USER:
|
||||
case KeyVerificationSession.SESSION_ACCEPTED:
|
||||
case KeyVerificationSession.REMOTE_SESSION_ACCEPTED:
|
||||
return "dialog-information";
|
||||
default:
|
||||
return "security-low";
|
||||
}
|
||||
}
|
||||
text: {
|
||||
switch (root.reason) {
|
||||
case KeyVerificationSession.NONE:
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#include "controller.h"
|
||||
#include "eventhandler.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/messagefiltermodel.h"
|
||||
#include "models/sortfilterroomtreemodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
@@ -43,6 +41,7 @@ RoomManager::RoomManager(QObject *parent)
|
||||
, m_messageFilterModel(new MessageFilterModel(this, m_timelineModel))
|
||||
, m_mediaMessageFilterModel(new MediaMessageFilterModel(this, m_messageFilterModel))
|
||||
, m_userListModel(new UserListModel(this))
|
||||
, m_widgetModel(new WidgetModel(this))
|
||||
{
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(UBUNTU_TOUCH)
|
||||
m_isMobile = true;
|
||||
@@ -55,6 +54,7 @@ RoomManager::RoomManager(QObject *parent)
|
||||
#endif
|
||||
|
||||
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
|
||||
m_widgetModel->setRoom(m_currentRoom);
|
||||
m_userListModel->setRoom(m_currentRoom);
|
||||
m_timelineModel->setRoom(m_currentRoom);
|
||||
m_sortFilterRoomTreeModel->setCurrentRoom(m_currentRoom);
|
||||
@@ -197,6 +197,11 @@ void RoomManager::activateUserModel()
|
||||
m_userListModel->activate();
|
||||
}
|
||||
|
||||
WidgetModel *RoomManager::widgetModel() const
|
||||
{
|
||||
return m_widgetModel;
|
||||
}
|
||||
|
||||
void RoomManager::resolveResource(const QString &idOrUri, const QString &action)
|
||||
{
|
||||
resolveResource(Uri{idOrUri}, action);
|
||||
@@ -514,7 +519,14 @@ void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom)
|
||||
|
||||
// This need to happen before the signal so TreeView.expandRecursively() can work nicely.
|
||||
m_sortFilterRoomTreeModel->setActiveSpaceId(m_currentSpaceId);
|
||||
m_sortFilterRoomTreeModel->setMode(m_currentSpaceId == u"DM"_s ? SortFilterRoomTreeModel::DirectChats : SortFilterRoomTreeModel::Rooms);
|
||||
|
||||
if (m_currentSpaceId == u"DM") {
|
||||
m_sortFilterRoomTreeModel->setMode(SortFilterRoomTreeModel::DirectChats);
|
||||
} else if (m_currentSpaceId.isEmpty()) {
|
||||
m_sortFilterRoomTreeModel->setMode(SortFilterRoomTreeModel::Rooms);
|
||||
} else {
|
||||
m_sortFilterRoomTreeModel->setMode(SortFilterRoomTreeModel::All);
|
||||
}
|
||||
|
||||
Q_EMIT currentSpaceChanged();
|
||||
if (m_connection) {
|
||||
@@ -578,7 +590,8 @@ void RoomManager::setCurrentRoom(const QString &roomId)
|
||||
return;
|
||||
}
|
||||
if (m_currentRoom->isDirectChat()) {
|
||||
if (m_currentSpaceId != "DM"_L1) {
|
||||
const auto roomsInSpace = SpaceHierarchyCache::instance().getRoomListForSpace(m_currentSpaceId, false);
|
||||
if (!roomsInSpace.contains(m_currentRoom->id()) && m_currentSpaceId != "DM"_L1) {
|
||||
setCurrentSpace("DM"_L1, false);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "models/sortfilterspacelistmodel.h"
|
||||
#include "models/timelinemodel.h"
|
||||
#include "models/userlistmodel.h"
|
||||
#include "models/widgetmodel.h"
|
||||
#include "neochatroommember.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
@@ -129,6 +130,14 @@ class RoomManager : public QObject, public UriResolverBase
|
||||
*/
|
||||
Q_PROPERTY(UserListModel *userListModel READ userListModel CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The WidgetModel that should be used for room widget visualisation.
|
||||
*
|
||||
* @note Available here so that the room page and drawer both have access to the
|
||||
* same model.
|
||||
*/
|
||||
Q_PROPERTY(WidgetModel *widgetModel READ widgetModel CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Whether a room is currently open in NeoChat.
|
||||
*
|
||||
@@ -160,6 +169,8 @@ public:
|
||||
UserListModel *userListModel() const;
|
||||
Q_INVOKABLE void activateUserModel();
|
||||
|
||||
WidgetModel *widgetModel() const;
|
||||
|
||||
/**
|
||||
* @brief Resolve the given resource.
|
||||
*
|
||||
@@ -356,6 +367,7 @@ private:
|
||||
MediaMessageFilterModel *m_mediaMessageFilterModel;
|
||||
|
||||
UserListModel *m_userListModel;
|
||||
WidgetModel *m_widgetModel;
|
||||
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <KWindowSystem>
|
||||
|
||||
#include "controller.h"
|
||||
#include "models/roomlistmodel.h"
|
||||
#include "models/sortfilterroomlistmodel.h"
|
||||
#include "roommanager.h"
|
||||
#include "windowcontroller.h"
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
@@ -87,9 +89,15 @@ QQC2.Control {
|
||||
displayHint: Kirigami.DisplayHint.IconOnly
|
||||
|
||||
onTriggered: {
|
||||
let dialog = (Clipboard.hasImage ? attachDialog : openFileDialog).createObject(root.QQC2.Overlay.overlay);
|
||||
dialog.chosen.connect(path => _private.chatBarCache.attachmentPath = path);
|
||||
dialog.open();
|
||||
if (Clipboard.hasImage) {
|
||||
let dialog = attachDialog.createObject(root.QQC2.Overlay.overlay) as AttachDialog;
|
||||
dialog.chosen.connect(path => _private.chatBarCache.attachmentPath = path);
|
||||
dialog.open();
|
||||
} else {
|
||||
let dialog = openFileDialog.createObject(root.QQC2.Overlay.overlay) as OpenFileDialog;
|
||||
dialog.chosen.connect(path => _private.chatBarCache.attachmentPath = path);
|
||||
dialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
tooltip: text
|
||||
@@ -122,9 +130,9 @@ QQC2.Control {
|
||||
displayHint: QQC2.AbstractButton.IconOnly
|
||||
|
||||
onTriggered: {
|
||||
locationChooser.createObject(QQC2.Overlay.overlay, {
|
||||
(locationChooser.createObject(QQC2.Overlay.overlay, {
|
||||
room: root.currentRoom
|
||||
}).open();
|
||||
}) as LocationChooser).open();
|
||||
}
|
||||
tooltip: text
|
||||
},
|
||||
@@ -136,9 +144,9 @@ QQC2.Control {
|
||||
displayHint: QQC2.AbstractButton.IconOnly
|
||||
|
||||
onTriggered: {
|
||||
newPollDialog.createObject(QQC2.Overlay.overlay, {
|
||||
(newPollDialog.createObject(QQC2.Overlay.overlay, {
|
||||
room: root.currentRoom
|
||||
}).open();
|
||||
}) as NewPollDialog).open();
|
||||
}
|
||||
tooltip: text
|
||||
},
|
||||
@@ -255,6 +263,7 @@ QQC2.Control {
|
||||
wrapMode: TextEdit.Wrap
|
||||
// This has to stay PlainText or else formatting starts breaking in strange ways
|
||||
textFormat: TextEdit.PlainText
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * NeoChatConfig.fontScale
|
||||
|
||||
Accessible.description: placeholderText
|
||||
|
||||
@@ -367,7 +376,9 @@ QQC2.Control {
|
||||
id: actionDelegate
|
||||
required property BusyAction modelData
|
||||
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
|
||||
onClicked: modelData.trigger()
|
||||
onClicked: if (!pieProgress.visible) {
|
||||
modelData.trigger()
|
||||
}
|
||||
|
||||
padding: Kirigami.Units.smallSpacing
|
||||
|
||||
@@ -375,7 +386,9 @@ QQC2.Control {
|
||||
QQC2.ToolTip.text: modelData.tooltip
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
contentItem: PieProgressBar {
|
||||
PieProgressBar {
|
||||
id: pieProgress
|
||||
anchors.fill: parent
|
||||
visible: actionDelegate.modelData.isBusy
|
||||
progress: root.currentRoom.fileUploadingProgress
|
||||
}
|
||||
@@ -402,7 +415,7 @@ QQC2.Control {
|
||||
ReplyComponent {
|
||||
id: replyComponent
|
||||
replyContentModel: ContentProvider.contentModelForEvent(root.currentRoom, _private.chatBarCache.replyId, true)
|
||||
Message.maxContentWidth: replyLoader.item.width
|
||||
Message.maxContentWidth: (replyLoader.item as Item).width
|
||||
|
||||
// When the user replies to a message and the preview is loaded, make sure the text field is focused again
|
||||
Component.onCompleted: textField.forceActiveFocus(Qt.OtherFocusReason)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.neochat
|
||||
|
||||
QQC2.ItemDelegate {
|
||||
id: root
|
||||
@@ -29,6 +30,7 @@ QQC2.ItemDelegate {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.family: "emoji"
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * NeoChatConfig.fontScale
|
||||
|
||||
Kirigami.Icon {
|
||||
width: Kirigami.Units.gridUnit * 0.5
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import QtCore as Core
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kquickimageeditor as KQuickImageEditor
|
||||
|
||||
Kirigami.Page {
|
||||
id: rootEditorView
|
||||
id: root
|
||||
|
||||
property bool resizing: false
|
||||
required property string imagePath
|
||||
@@ -26,7 +25,7 @@ Kirigami.Page {
|
||||
function crop() {
|
||||
const ratioX = editImage.paintedWidth / editImage.nativeWidth;
|
||||
const ratioY = editImage.paintedHeight / editImage.nativeHeight;
|
||||
rootEditorView.resizing = false;
|
||||
root.resizing = false;
|
||||
imageDoc.crop(selectionTool.selectionX / ratioX, selectionTool.selectionY / ratioY, selectionTool.selectionWidth / ratioX, selectionTool.selectionHeight / ratioY);
|
||||
}
|
||||
|
||||
@@ -43,9 +42,9 @@ Kirigami.Page {
|
||||
text: i18nc("@action:button Accept image modification", "Accept")
|
||||
icon.name: "dialog-ok"
|
||||
onTriggered: {
|
||||
let newPath = Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + (new Date()).getTime() + "." + imagePath.split('.').pop();
|
||||
let newPath = Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + (new Date()).getTime() + "." + root.imagePath.split('.').pop();
|
||||
if (imageDoc.saveAs(newPath)) {
|
||||
newPathChanged(newPath);
|
||||
root.newPathChanged(newPath);
|
||||
} else {
|
||||
msg.type = Kirigami.MessageType.Error;
|
||||
msg.text = i18n("Unable to save file. Check if you have the correct permission to edit the cache directory.");
|
||||
@@ -80,12 +79,12 @@ Kirigami.Page {
|
||||
|
||||
KQuickImageEditor.ImageDocument {
|
||||
id: imageDoc
|
||||
path: rootEditorView.imagePath
|
||||
path: root.imagePath
|
||||
}
|
||||
|
||||
KQuickImageEditor.SelectionTool {
|
||||
id: selectionTool
|
||||
visible: rootEditorView.resizing
|
||||
visible: root.resizing
|
||||
width: editImage.paintedWidth
|
||||
height: editImage.paintedHeight
|
||||
x: editImage.horizontalPadding
|
||||
@@ -101,7 +100,7 @@ Kirigami.Page {
|
||||
Connections {
|
||||
target: selectionTool.selectionArea
|
||||
function onDoubleClicked() {
|
||||
rootEditorView.crop();
|
||||
root.crop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,8 +118,8 @@ Kirigami.Page {
|
||||
display: QQC2.Button.TextBesideIcon
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
icon.name: rootEditorView.resizing ? "dialog-cancel" : "transform-crop"
|
||||
text: rootEditorView.resizing ? i18n("Cancel") : i18nc("@action:button Crop an image", "Crop")
|
||||
icon.name: root.resizing ? "dialog-cancel" : "transform-crop"
|
||||
text: root.resizing ? i18n("Cancel") : i18nc("@action:button Crop an image", "Crop")
|
||||
onTriggered: {
|
||||
resizeRectangle.width = editImage.paintedWidth;
|
||||
resizeRectangle.height = editImage.paintedHeight;
|
||||
@@ -130,38 +129,38 @@ Kirigami.Page {
|
||||
resizeRectangle.insideY = 100;
|
||||
resizeRectangle.insideWidth = 100;
|
||||
resizeRectangle.insideHeight = 100;
|
||||
rootEditorView.resizing = !rootEditorView.resizing;
|
||||
root.resizing = !root.resizing;
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "dialog-ok"
|
||||
visible: rootEditorView.resizing
|
||||
visible: root.resizing
|
||||
text: i18nc("@action:button Crop an image", "Crop")
|
||||
onTriggered: rootEditorView.crop()
|
||||
onTriggered: root.crop()
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "object-rotate-left"
|
||||
text: i18nc("@action:button Rotate an image to the left", "Rotate left")
|
||||
onTriggered: imageDoc.rotate(-90)
|
||||
visible: !rootEditorView.resizing
|
||||
visible: !root.resizing
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "object-rotate-right"
|
||||
text: i18nc("@action:button Rotate an image to the right", "Rotate right")
|
||||
onTriggered: imageDoc.rotate(90)
|
||||
visible: !rootEditorView.resizing
|
||||
visible: !root.resizing
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "object-flip-vertical"
|
||||
text: i18nc("@action:button Mirror an image vertically", "Flip")
|
||||
onTriggered: imageDoc.mirror(false, true)
|
||||
visible: !rootEditorView.resizing
|
||||
visible: !root.resizing
|
||||
},
|
||||
Kirigami.Action {
|
||||
icon.name: "object-flip-horizontal"
|
||||
text: i18nc("@action:button Mirror an image horizontally", "Mirror")
|
||||
onTriggered: imageDoc.mirror(true, false)
|
||||
visible: !rootEditorView.resizing
|
||||
visible: !root.resizing
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
@@ -23,8 +25,9 @@ ColumnLayout {
|
||||
Repeater {
|
||||
model: root.connection.accountDataEventTypes
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
required property string modelData
|
||||
text: modelData
|
||||
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
onClicked: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
sourceText: root.connection.accountDataJsonString(modelData)
|
||||
}, {
|
||||
title: i18nc("@title:window", "Event Source"),
|
||||
|
||||
@@ -17,6 +17,8 @@ ecm_add_qml_module(Devtools GENERATE_PLUGIN_SOURCE
|
||||
models/statefiltermodel.cpp
|
||||
models/statekeysmodel.cpp
|
||||
models/statemodel.cpp
|
||||
DEPENDENCIES
|
||||
QtCore
|
||||
)
|
||||
|
||||
target_include_directories(Devtools PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
|
||||
|
||||
@@ -17,6 +17,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
property NeoChatRoom room
|
||||
required property NeoChatConnection connection
|
||||
property alias currentTabIndex: tabBar.currentIndex
|
||||
|
||||
title: i18nc("@title", "Developer Tools")
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
@@ -25,7 +27,7 @@ ColumnLayout {
|
||||
description: i18nc("@info", "Click to choose a room");
|
||||
|
||||
onClicked: {
|
||||
let dialog = root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
||||
let dialog = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
||||
connection: root.connection,
|
||||
}, {
|
||||
title: i18nc("@title:dialog", "Choose Room"),
|
||||
@@ -49,8 +51,9 @@ ColumnLayout {
|
||||
id: roomAccountData
|
||||
model: root.room.accountDataEventTypes
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
required property string modelData
|
||||
text: modelData
|
||||
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
onClicked: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
sourceText: root.room.roomAcountDataJson(text)
|
||||
}, {
|
||||
title: i18n("Event Source"),
|
||||
@@ -74,15 +77,18 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: model.type
|
||||
description: i18ncp("'Event' being some JSON data, not something physically happening.", "%1 event of this type", "%1 events of this type", model.eventCount)
|
||||
required property string type
|
||||
required property int eventCount
|
||||
required property string stateKey
|
||||
text: type
|
||||
description: i18ncp("'Event' being some JSON data, not something physically happening.", "%1 event of this type", "%1 events of this type", eventCount)
|
||||
onClicked: {
|
||||
if (model.eventCount === 1) {
|
||||
openEventSource(model.type, model.stateKey);
|
||||
if (eventCount === 1) {
|
||||
root.openEventSource(type, stateKey);
|
||||
} else {
|
||||
root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
|
||||
(root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
|
||||
room: root.room,
|
||||
eventType: model.type
|
||||
eventType: type
|
||||
}, {
|
||||
title: i18nc("'Event' being some JSON data, not something physically happening.", "Event Information")
|
||||
});
|
||||
@@ -92,7 +98,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
function openEventSource(type: string, stateKey: string): void {
|
||||
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
onClicked: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
model: stateModel,
|
||||
allowEdit: true,
|
||||
room: root.room,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
@@ -30,14 +31,15 @@ FormCard.FormCardPage {
|
||||
}
|
||||
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: model.stateKey
|
||||
onClicked: openEventSource(model.stateKey)
|
||||
required property string stateKey
|
||||
text: stateKey
|
||||
onClicked: root.openEventSource(stateKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openEventSource(stateKey: string): void {
|
||||
root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
(Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
model: stateKeysModel,
|
||||
allowEdit: true,
|
||||
room: root.room,
|
||||
|
||||
@@ -45,6 +45,8 @@ target_sources(LibNeoChat PRIVATE
|
||||
models/stickermodel.cpp
|
||||
models/userfiltermodel.cpp
|
||||
models/userlistmodel.cpp
|
||||
models/widgetmodel.cpp
|
||||
models/widgetmodel.h
|
||||
)
|
||||
|
||||
if (TARGET KF6::KIOWidgets)
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
ServerNotice, /**< Official messages from the server. */
|
||||
Deprioritized, /**< The room is set as low priority. */
|
||||
Space, /**< The room is a space. */
|
||||
AddDirect, /**< So we can show the add friend delegate. */
|
||||
AddDirect, /**< So we can show the add direct message delegate. */
|
||||
TypesCount, /**< Number of different types (this should always be last). */
|
||||
};
|
||||
Q_ENUM(Types);
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
case NeoChatRoomType::Favorite:
|
||||
return i18n("Favorite");
|
||||
case NeoChatRoomType::Direct:
|
||||
return i18n("Friends");
|
||||
return i18n("Direct Messages");
|
||||
case NeoChatRoomType::Normal:
|
||||
return i18n("Normal");
|
||||
case NeoChatRoomType::Deprioritized:
|
||||
|
||||
@@ -89,7 +89,7 @@ public:
|
||||
case Parameter::MostHighlights:
|
||||
return i18nc("@info", "Rooms with the most highlighted messages are higher");
|
||||
case Parameter::LastActive:
|
||||
return i18nc("@info", "Rooms with the newer messages are higher");
|
||||
return i18nc("@info", "Rooms with newer events are higher");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include <Quotient/events/encryptionevent.h>
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/events/eventcontent.h>
|
||||
#include <Quotient/events/reactionevent.h>
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/roomavatarevent.h>
|
||||
@@ -196,20 +195,25 @@ bool EventHandler::isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::TextFormat EventHandler::messageBodyInputFormat(const Quotient::RoomMessageEvent &event)
|
||||
Qt::TextFormat EventHandler::messageBodyInputFormat(const Quotient::RoomEvent &event)
|
||||
{
|
||||
if (event.isRedacted() && !event.isStateEvent()) {
|
||||
return Qt::RichText;
|
||||
}
|
||||
|
||||
if (event.mimeType().name() == "text/plain"_L1) {
|
||||
auto msgEvent = eventCast<const Quotient::RoomMessageEvent>(&event);
|
||||
if (!msgEvent) {
|
||||
return Qt::PlainText;
|
||||
}
|
||||
|
||||
if (msgEvent->mimeType().name() == "text/plain"_L1) {
|
||||
return Qt::PlainText;
|
||||
} else {
|
||||
return Qt::RichText;
|
||||
}
|
||||
}
|
||||
|
||||
QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
QString EventHandler::rawMessageBody(const RoomEvent &event)
|
||||
{
|
||||
if (event.isRedacted() && !event.isStateEvent()) {
|
||||
auto reason = event.redactedBecause()->reason();
|
||||
@@ -218,21 +222,26 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
|
||||
QString body;
|
||||
|
||||
if (event.has<EventContent::FileContent>()) {
|
||||
auto msgEvent = eventCast<const Quotient::RoomMessageEvent>(&event);
|
||||
if (!msgEvent) {
|
||||
return body;
|
||||
}
|
||||
|
||||
if (msgEvent->has<EventContent::FileContent>()) {
|
||||
// if filename is given or body is equal to filename,
|
||||
// then body is a caption
|
||||
QString filename = event.get<EventContent::FileContent>()->originalName;
|
||||
QString body = event.plainBody();
|
||||
QString filename = msgEvent->get<EventContent::FileContent>()->originalName;
|
||||
QString body = msgEvent->plainBody();
|
||||
if (filename.isEmpty() || filename == body) {
|
||||
return QString();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
if (event.has<EventContent::TextContent>() && event.content()) {
|
||||
body = event.get<EventContent::TextContent>()->body;
|
||||
if (msgEvent->has<EventContent::TextContent>() && msgEvent->content()) {
|
||||
body = msgEvent->get<EventContent::TextContent>()->body;
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
body = msgEvent->plainBody();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
@@ -451,6 +460,9 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
||||
return i18nc("[User] joined a [voice/video] call", "joined a call");
|
||||
}
|
||||
}
|
||||
if (e.matrixType() == "io.element.integrations.installations"_L1) {
|
||||
return i18nc("[User] configured an extension", "configured an extension");
|
||||
}
|
||||
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
||||
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
|
||||
},
|
||||
@@ -664,6 +676,9 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
|
||||
return i18nc("[User] joined a [voice/video] call", "%1 joined a call", senderString);
|
||||
}
|
||||
}
|
||||
if (e.matrixType() == "io.element.integrations.installations"_L1) {
|
||||
return i18nc("[User] configured an extension", "%1 configured an extension", senderString);
|
||||
}
|
||||
return i18n("%1 updated the state", senderString);
|
||||
},
|
||||
[senderString](const PollStartEvent &) {
|
||||
|
||||
@@ -122,15 +122,15 @@ public:
|
||||
* I.e. if the message has only a body the format will be Qt::PlainText, if it
|
||||
* has a formatted body it will be Qt::RichText.
|
||||
*/
|
||||
static Qt::TextFormat messageBodyInputFormat(const Quotient::RoomMessageEvent &event);
|
||||
static Qt::TextFormat messageBodyInputFormat(const Quotient::RoomEvent &event);
|
||||
|
||||
/**
|
||||
* @brief Output a string for the room message content without any formatting.
|
||||
* @brief Output a string for the 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);
|
||||
static QString rawMessageBody(const Quotient::RoomEvent &event);
|
||||
|
||||
/**
|
||||
* @brief Output a string for the message content ready for display in a rich text field.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "pollevent.h"
|
||||
#include <Quotient/converters.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
|
||||
@@ -146,9 +146,13 @@ bool UserListModel::event(QEvent *event)
|
||||
|
||||
void UserListModel::memberJoined(const Quotient::RoomMember &member)
|
||||
{
|
||||
auto pos = findUserPos(member);
|
||||
beginInsertRows(QModelIndex(), pos, pos);
|
||||
m_members.insert(pos, member.id());
|
||||
if (m_members.contains(member.id())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int row = m_members.size();
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
m_members.append(member.id());
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
@@ -168,8 +172,6 @@ void UserListModel::refreshMember(const Quotient::RoomMember &member, const QLis
|
||||
{
|
||||
auto pos = findUserPos(member);
|
||||
if (pos != m_members.size()) {
|
||||
// The update will have changed the state event so we need to insert the updated member object.
|
||||
m_members.insert(pos, member.id());
|
||||
Q_EMIT dataChanged(index(pos), index(pos), roles);
|
||||
} else {
|
||||
qWarning() << "Trying to access a room member not in the user list";
|
||||
|
||||
356
src/libneochat/models/widgetmodel.cpp
Normal file
356
src/libneochat/models/widgetmodel.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
// SPDX-FileCopyrightText: 2025 Arno Rehn <arno@arnorehn.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "widgetmodel.h"
|
||||
|
||||
#include <Quotient/jobs/downloadfilejob.h>
|
||||
#include <Quotient/user.h>
|
||||
#include <widgetevent.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// This URL will take us to a jitsi room that is configured on a best effort basis.
|
||||
// Critically, it appears that we cannot pass avatars via the "fragment API".
|
||||
// The important stuff is working though: conference id, user name, subject.
|
||||
//
|
||||
// The alternative would be to rely on a Jitsi wrapper like https://app.element.io/jitsi.html.
|
||||
// This one parses the fragment and then uses the the actual Jitsi IFrame API to set
|
||||
// up the Jitsi meeting. Nice, but ties us to that external wrapper.
|
||||
//
|
||||
// Or we ship our own wrapper as a local HTML file and open that one?
|
||||
constexpr char JitsiMeetUrlTemplate[] =
|
||||
"https://$domain/$conferenceId"
|
||||
"#jitsi_meet_external_api_id=0"
|
||||
"&config.subject=\"$roomName\""
|
||||
"&config.startAudioOnly=$isAudioOnly"
|
||||
"&config.startWithAudioMuted=$startWithAudioMuted"
|
||||
"&config.startWithVideoMuted=$startWithVideoMuted"
|
||||
"&isVideoChannel=$isVideoChannel"
|
||||
"&userInfo.displayName=\"$matrix_display_name\"";
|
||||
|
||||
// Same thing with etherpad, as long as it's on scalar.vector.im
|
||||
constexpr char EtherpadScalarVectorImUrl[] = "https://scalar.vector.im/api/widgets/etherpad.html";
|
||||
constexpr char EtherpadUrlTemplate[] =
|
||||
"https://etherpad.integrations.element.io/p/"
|
||||
"$padName"
|
||||
"?showControls=true"
|
||||
"&showChat=false"
|
||||
"&chatAndUsers=false"
|
||||
"&alwaysShowChat=false"
|
||||
"&showLineNumbers=true"
|
||||
"&useMonospaceFont=false"
|
||||
"&userName=$matrix_user_id";
|
||||
|
||||
QUrl avatarUrl(NeoChatRoom *room)
|
||||
{
|
||||
const auto avatarUrl = room->member(room->connection()->userId()).avatarUrl();
|
||||
return room->connection()->getUrlForApi<DownloadFileJob>(avatarUrl);
|
||||
}
|
||||
|
||||
// Many widgets require proper interfacing with integration managers, but this needs authentication
|
||||
// with an integration manager: https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1961-integrations-auth.md
|
||||
// Also requires accepting the terms: https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2140-terms-of-service-2.md
|
||||
// Example:
|
||||
// curl -X POST "https://scalar.vector.im/_matrix/integrations/v1/register" \
|
||||
// -H "Content-Type: application/json" \
|
||||
// --data '<OpenIdCredentials>'
|
||||
// Then append the resulting json key-value pairs as query items to the widget url.
|
||||
QUrl buildWidgetUrl(QByteArray templateString, NeoChatRoom *room, const QJsonObject &data)
|
||||
{
|
||||
constexpr auto enc = [](const auto &s) {
|
||||
return QUrl::toPercentEncoding(s);
|
||||
};
|
||||
|
||||
templateString.replace("$matrix_user_id"_L1, enc(room->connection()->userId()))
|
||||
.replace("$matrix_room_id"_L1, enc(room->id()))
|
||||
.replace("$matrix_display_name"_L1, enc(room->member(room->connection()->userId()).displayName()))
|
||||
.replace("$matrix_avatar_url"_L1, avatarUrl(room).toEncoded());
|
||||
|
||||
for (auto it = data.begin(); it != data.end(); ++it) {
|
||||
const QByteArray key = '$' + it.key().toUtf8();
|
||||
templateString.replace(key, enc(it.value().toString()));
|
||||
}
|
||||
|
||||
return QUrl::fromEncoded(templateString);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class WidgetModelPrivate
|
||||
{
|
||||
Q_DISABLE_COPY(WidgetModelPrivate);
|
||||
|
||||
public:
|
||||
WidgetModelPrivate(WidgetModel *q)
|
||||
: q_ptr(q)
|
||||
{
|
||||
}
|
||||
|
||||
Q_DECLARE_PUBLIC(WidgetModel)
|
||||
WidgetModel *const q_ptr;
|
||||
|
||||
// clang-format off
|
||||
struct NoModelResetTag_t {};
|
||||
static constexpr NoModelResetTag_t NoModelReset {};
|
||||
// clang-format on
|
||||
|
||||
void reload();
|
||||
void reload(NoModelResetTag_t);
|
||||
void updateWidgets(Quotient::RoomEventsRange events);
|
||||
void handleEvent(const Quotient::RoomEvent *event);
|
||||
void handlePendingEvent(const Quotient::RoomEvent *event);
|
||||
void buildJitsiIndex();
|
||||
|
||||
NeoChatRoom *room = nullptr;
|
||||
QMap<QString, const WidgetEvent *> state;
|
||||
int jitsiIndex = -1;
|
||||
};
|
||||
|
||||
WidgetModel::WidgetModel(QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
, d_ptr(std::make_unique<WidgetModelPrivate>(this))
|
||||
{
|
||||
}
|
||||
|
||||
WidgetModel::~WidgetModel()
|
||||
{
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> WidgetModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{TextRole, "text"},
|
||||
{UrlRole, "url"},
|
||||
{TypeRole, "type"},
|
||||
};
|
||||
}
|
||||
|
||||
QVariant WidgetModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
Q_D(const WidgetModel);
|
||||
|
||||
const int row = index.row();
|
||||
if (row < 0 || row >= d->state.count()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto *ev = std::next(d->state.begin(), row).value();
|
||||
const auto type = ev->contentPart<QString>("type"_L1);
|
||||
const auto url = ev->contentPart<QString>("url"_L1).toUtf8();
|
||||
|
||||
switch (role) {
|
||||
case TextRole:
|
||||
return ev->contentPart<QString>("name"_L1);
|
||||
case TypeRole:
|
||||
return type;
|
||||
case UrlRole: {
|
||||
QByteArray urlTemplate = url;
|
||||
if (type == "jitsi"_L1) {
|
||||
// Jitsi is special-cased even in Element. The URL that we get from the
|
||||
// widget is just "https://scalar.vector.im/api/widgets/jitsi.html" which
|
||||
// is not sufficient.
|
||||
urlTemplate = JitsiMeetUrlTemplate;
|
||||
} else if (type == "m.etherpad"_L1 && url.startsWith(EtherpadScalarVectorImUrl)) {
|
||||
// Etherpad is not special-cased, but the scalar.vector.im wrapper requires authentication
|
||||
// with the integration manager. We can just resolve this to the actual etherpad instance
|
||||
// to skip integration manager auth.
|
||||
urlTemplate = EtherpadUrlTemplate;
|
||||
}
|
||||
return buildWidgetUrl(urlTemplate, room(), ev->contentPart<QJsonObject>("data"_L1));
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int WidgetModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_D(const WidgetModel);
|
||||
Q_UNUSED(parent);
|
||||
return d->state.count();
|
||||
}
|
||||
|
||||
void WidgetModelPrivate::reload(NoModelResetTag_t)
|
||||
{
|
||||
state.clear();
|
||||
|
||||
if (room) {
|
||||
const auto events = room->currentState().eventsOfType(Quotient::WidgetEvent::MetaType.matrixId);
|
||||
for (auto ev : events) {
|
||||
handleEvent(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetModelPrivate::reload()
|
||||
{
|
||||
Q_Q(WidgetModel);
|
||||
|
||||
q->beginResetModel();
|
||||
|
||||
reload(NoModelReset);
|
||||
buildJitsiIndex();
|
||||
|
||||
q->endResetModel();
|
||||
}
|
||||
|
||||
void WidgetModelPrivate::updateWidgets(Quotient::RoomEventsRange events)
|
||||
{
|
||||
using namespace std::ranges;
|
||||
|
||||
constexpr auto isWidgetEvent = [](auto &&ev) {
|
||||
return ev->template is<WidgetEvent>();
|
||||
};
|
||||
|
||||
if (any_of(events, isWidgetEvent)) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetModelPrivate::handleEvent(const Quotient::RoomEvent *event)
|
||||
{
|
||||
const WidgetEvent *widgetEvent = eventCast<const WidgetEvent>(event);
|
||||
if (!widgetEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = state.find(widgetEvent->stateKey());
|
||||
if (it != state.end() && widgetEvent->replacedState() == it.value()->id()) {
|
||||
// we alrady have this and it should be...
|
||||
if (widgetEvent->contentJson().isEmpty()) {
|
||||
// ... removed because replacement is empty
|
||||
state.erase(it);
|
||||
} else {
|
||||
// replaced
|
||||
it.value() = widgetEvent;
|
||||
}
|
||||
} else if (!widgetEvent->contentJson().isEmpty()) {
|
||||
// insert if not empty
|
||||
it = state.insert(widgetEvent->stateKey(), widgetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetModelPrivate::handlePendingEvent(const RoomEvent *event)
|
||||
{
|
||||
Q_Q(WidgetModel);
|
||||
|
||||
if (!event->is<WidgetEvent>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
q->beginResetModel();
|
||||
|
||||
reload(NoModelReset);
|
||||
handleEvent(event);
|
||||
buildJitsiIndex();
|
||||
|
||||
q->endResetModel();
|
||||
}
|
||||
|
||||
void WidgetModelPrivate::buildJitsiIndex()
|
||||
{
|
||||
Q_Q(WidgetModel);
|
||||
|
||||
const auto newIndex = [&] -> int {
|
||||
auto it = std::find_if(state.cbegin(), state.cend(), [](const WidgetEvent *e) {
|
||||
return e->contentPart<QString>("type"_L1) == "jitsi"_L1;
|
||||
});
|
||||
|
||||
if (it == state.cend()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return std::distance(state.cbegin(), it);
|
||||
}();
|
||||
|
||||
if (newIndex != jitsiIndex) {
|
||||
jitsiIndex = newIndex;
|
||||
Q_EMIT q->jitsiIndexChanged();
|
||||
}
|
||||
}
|
||||
|
||||
NeoChatRoom *WidgetModel::room() const
|
||||
{
|
||||
Q_D(const WidgetModel);
|
||||
return d->room;
|
||||
}
|
||||
|
||||
void WidgetModel::setRoom(NeoChatRoom *newRoom)
|
||||
{
|
||||
Q_D(WidgetModel);
|
||||
|
||||
if (d->room == newRoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (d->room) {
|
||||
disconnect(d->room, &NeoChatRoom::baseStateLoaded, this, nullptr);
|
||||
disconnect(d->room, &NeoChatRoom::aboutToAddNewMessages, this, nullptr);
|
||||
disconnect(d->room, &NeoChatRoom::pendingEventAboutToAdd, this, nullptr);
|
||||
}
|
||||
|
||||
d->room = newRoom;
|
||||
|
||||
if (d->room) {
|
||||
connect(d->room, &NeoChatRoom::baseStateLoaded, this, [d] {
|
||||
d->reload();
|
||||
});
|
||||
connect(d->room, &NeoChatRoom::aboutToAddNewMessages, this, [d](Quotient::RoomEventsRange events) {
|
||||
d->updateWidgets(events);
|
||||
});
|
||||
connect(d->room, &NeoChatRoom::pendingEventAboutToAdd, this, [d](Quotient::RoomEvent *event) {
|
||||
d->handlePendingEvent(event);
|
||||
});
|
||||
}
|
||||
d->reload();
|
||||
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
int WidgetModel::jitsiIndex() const
|
||||
{
|
||||
Q_D(const WidgetModel);
|
||||
|
||||
return d->jitsiIndex;
|
||||
}
|
||||
|
||||
QUrl WidgetModel::addJitsiConference()
|
||||
{
|
||||
// URL is not spec-compliant, but this is what Element does as well.
|
||||
const auto conferenceId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
// clang-format off
|
||||
const QJsonObject content{
|
||||
{"name"_L1, "Jitsi Meet"_L1},
|
||||
{"type"_L1, "jitsi"_L1},
|
||||
{"url"_L1, "https://scalar.vector.im/api/widgets/jitsi.html"_L1},
|
||||
{"data"_L1, QJsonObject{
|
||||
{"conferenceId"_L1, conferenceId},
|
||||
{"domain"_L1, "meet.element.io"_L1}, // TODO: make domain configurable
|
||||
{"isAudioOnly"_L1, false},
|
||||
{"roomName"_L1, room()->displayName()},
|
||||
}},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Re-use conferenceId as state_key
|
||||
room()->setState(WidgetEvent::MetaType.matrixId, conferenceId, content);
|
||||
|
||||
return buildWidgetUrl(JitsiMeetUrlTemplate, room(), content["data"_L1].toObject());
|
||||
}
|
||||
|
||||
bool WidgetModel::removeWidget(int index)
|
||||
{
|
||||
Q_D(const WidgetModel);
|
||||
|
||||
if (index < 0 || index >= rowCount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto stateKey = std::next(d->state.begin(), index).value()->stateKey();
|
||||
room()->setState(WidgetEvent::MetaType.matrixId, stateKey, QJsonObject{});
|
||||
|
||||
return true;
|
||||
}
|
||||
74
src/libneochat/models/widgetmodel.h
Normal file
74
src/libneochat/models/widgetmodel.h
Normal file
@@ -0,0 +1,74 @@
|
||||
// SPDX-FileCopyrightText: 2025 Arno Rehn <arno@arnorehn.de>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#ifndef WIDGETMODEL_H
|
||||
#define WIDGETMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
class WidgetModelPrivate;
|
||||
|
||||
/**
|
||||
* @class WidgetModel
|
||||
*
|
||||
* `WidgetModel` provides a list model of widgets for a given room.
|
||||
*
|
||||
* It also supports adding and removing widgets.
|
||||
*/
|
||||
class WidgetModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The room for which this model lists the widgets
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
/**
|
||||
* @brief The row index of the first Jitsi widget
|
||||
*/
|
||||
Q_PROPERTY(int jitsiIndex READ jitsiIndex NOTIFY jitsiIndexChanged)
|
||||
public:
|
||||
enum Roles {
|
||||
TextRole = Qt::DisplayRole,
|
||||
UrlRole = Qt::UserRole,
|
||||
TypeRole,
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit WidgetModel(QObject *parent = nullptr);
|
||||
~WidgetModel() override;
|
||||
|
||||
NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *newRoom);
|
||||
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = {}) const override;
|
||||
|
||||
int jitsiIndex() const;
|
||||
|
||||
/**
|
||||
* @brief Adds a new Jitsi widget
|
||||
* @return The URL of the newly added Jitsi conference
|
||||
*/
|
||||
Q_INVOKABLE QUrl addJitsiConference();
|
||||
/**
|
||||
* @brief Removes the widget at @p index
|
||||
* @return `true` on success, `false` otherwise
|
||||
*/
|
||||
Q_INVOKABLE bool removeWidget(int index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void jitsiIndexChanged();
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(WidgetModel)
|
||||
const std::unique_ptr<WidgetModelPrivate> d_ptr;
|
||||
};
|
||||
|
||||
#endif // WIDGETMODEL_H
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "neochatroom.h"
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <qt6keychain/keychain.h>
|
||||
@@ -96,6 +95,7 @@ void NeoChatConnection::connectSignals()
|
||||
Q_EMIT directChatsHaveHighlightNotificationsChanged();
|
||||
});
|
||||
}
|
||||
Q_EMIT roomInvitesChanged();
|
||||
connect(room, &Room::unreadStatsChanged, this, [this]() {
|
||||
refreshBadgeNotificationCount();
|
||||
Q_EMIT homeNotificationsChanged();
|
||||
@@ -453,15 +453,20 @@ bool NeoChatConnection::homeHaveHighlightNotifications() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NeoChatConnection::directChatInvites() const
|
||||
qsizetype NeoChatConnection::directChatInvites() const
|
||||
{
|
||||
auto inviteRooms = rooms(JoinState::Invite);
|
||||
for (const auto inviteRoom : inviteRooms) {
|
||||
if (inviteRoom->isDirectChat()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
const auto inviteRooms = rooms(JoinState::Invite);
|
||||
return std::ranges::count_if(inviteRooms, [](const auto room) {
|
||||
return room->isDirectChat();
|
||||
});
|
||||
}
|
||||
|
||||
qsizetype NeoChatConnection::roomInvites() const
|
||||
{
|
||||
const auto inviteRooms = rooms(JoinState::Invite);
|
||||
return std::ranges::count_if(inviteRooms, [](const auto room) {
|
||||
return !room->isDirectChat();
|
||||
});
|
||||
}
|
||||
|
||||
QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
|
||||
|
||||
@@ -66,9 +66,14 @@ class NeoChatConnection : public Quotient::Connection
|
||||
Q_PROPERTY(bool homeHaveHighlightNotifications READ homeHaveHighlightNotifications NOTIFY homeHaveHighlightNotificationsChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether there is at least one invite to a direct chat.
|
||||
* @brief The number of invites to 1-on-1 direct chats.
|
||||
*/
|
||||
Q_PROPERTY(bool directChatInvites READ directChatInvites NOTIFY directChatInvitesChanged)
|
||||
Q_PROPERTY(qsizetype directChatInvites READ directChatInvites NOTIFY directChatInvitesChanged)
|
||||
|
||||
/**
|
||||
* @brief The number of pending, normal room invites.
|
||||
*/
|
||||
Q_PROPERTY(qsizetype roomInvites READ roomInvites NOTIFY roomInvitesChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the server supports querying a user's mutual rooms.
|
||||
@@ -200,7 +205,8 @@ public:
|
||||
*/
|
||||
static void setKeywordPushRuleDefault(PushRuleAction::Action defaultAction);
|
||||
|
||||
bool directChatInvites() const;
|
||||
qsizetype directChatInvites() const;
|
||||
qsizetype roomInvites() const;
|
||||
|
||||
// note: this is intentionally a copied QString because
|
||||
// the reference could be destroyed before the task is finished
|
||||
@@ -225,6 +231,7 @@ Q_SIGNALS:
|
||||
void homeNotificationsChanged();
|
||||
void homeHaveHighlightNotificationsChanged();
|
||||
void directChatInvitesChanged();
|
||||
void roomInvitesChanged();
|
||||
void passwordStatus(NeoChatConnection::PasswordStatus status);
|
||||
void userConsentRequired(QUrl url);
|
||||
void badgeNotificationCountChanged(int count);
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
#include "chatbarcache.h"
|
||||
#include "clipboard.h"
|
||||
#include "eventhandler.h"
|
||||
#include "events/pollevent.h"
|
||||
#include "filetransferpseudojob.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "roomlastmessageprovider.h"
|
||||
@@ -1181,6 +1180,9 @@ void NeoChatRoom::loadPinnedMessage()
|
||||
const QString &mostRecentEventId = events.last();
|
||||
connection()->callApi<GetOneRoomEventJob>(id(), mostRecentEventId).then([this](const auto &job) {
|
||||
auto event = fromJson<event_ptr_tt<RoomEvent>>(job->jsonData());
|
||||
if (auto encEv = eventCast<EncryptedEvent>(event.get())) {
|
||||
event = decryptMessage(*encEv);
|
||||
}
|
||||
m_pinnedMessage = EventHandler::richBody(this, event.get());
|
||||
Q_EMIT pinnedMessageChanged();
|
||||
});
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.qmlmodels
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
@@ -129,8 +130,8 @@ SearchPage {
|
||||
function openManualRoomDialog() {
|
||||
let dialog = manualRoomDialog.createObject(root.QQC2.Overlay.overlay, {
|
||||
connection: root.connection
|
||||
});
|
||||
dialog.parent = root.Window.window.overlay;
|
||||
}) as ManualRoomDialog;
|
||||
dialog.parent = root.QQC2.Overlay.overlay;
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
|
||||
root.closeDialog();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
|
||||
@@ -39,11 +39,11 @@ LoginStep {
|
||||
text: LoginHelper.isLoggedIn ? i18n("Already logged in") : (LoginHelper.testing && matrixIdField.acceptableInput) ? i18n("Loading…") : i18nc("@action:button", "Continue")
|
||||
onTriggered: {
|
||||
if (LoginHelper.supportsSso && LoginHelper.supportsPassword) {
|
||||
processed("LoginMethod");
|
||||
root.processed("LoginMethod");
|
||||
} else if (LoginHelper.supportsSso) {
|
||||
processed("Sso");
|
||||
root.processed("Sso");
|
||||
} else {
|
||||
processed("Password");
|
||||
root.processed("Password");
|
||||
}
|
||||
}
|
||||
enabled: LoginHelper.homeserverReachable
|
||||
|
||||
@@ -18,12 +18,12 @@ LoginStep {
|
||||
FormCard.FormButtonDelegate {
|
||||
id: loginPasswordButton
|
||||
text: i18nc("@action:button", "Login with password")
|
||||
onClicked: processed("Password")
|
||||
onClicked: root.processed("Password")
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: loginSsoButton
|
||||
text: i18nc("@action:button", "Login with single sign-on")
|
||||
onClicked: processed("Sso")
|
||||
onClicked: root.processed("Sso")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
@@ -15,7 +14,7 @@ LoginStep {
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onConnected() {
|
||||
processed("Loading");
|
||||
root.processed("Loading");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +45,6 @@ LoginStep {
|
||||
}
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: processed("Login")
|
||||
onTriggered: root.processed("Login")
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user