Compare commits
3 Commits
work/fix_c
...
work/carl/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8177c1f1bc | ||
|
|
80171748d8 | ||
|
|
6aa2e586de |
@@ -77,7 +77,7 @@ ecm_setup_version(${PROJECT_VERSION}
|
||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||
)
|
||||
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg WebView)
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
android:versionName="${versionName}"
|
||||
android:versionCode="${versionCode}"
|
||||
android:installLocation="auto">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat" android:usesCleartextTraffic="true">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||
android:label="NeoChat"
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
|
||||
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
|
||||
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
|
||||
<summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
|
||||
<summary xml:lang="nl">Met uw vrienden chatten op matrix</summary>
|
||||
<summary xml:lang="nn">Prat med vennar på Matrix</summary>
|
||||
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
|
||||
@@ -268,6 +267,52 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<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::windows_store::screenshots::1::image">https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Timeline.png</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption">Main view with room list, chat, and room information</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="nl">Hoofdweergave met lijst met rooms, chat en roominformatie</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="x-test">xxMain view with room list, chat, and room informationxx</value>
|
||||
<value key="KDE::windows_store::screenshots::2::image">https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption">Login screen</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ar">شاشة الدخول</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ca">Pantalla d'inici de sessió</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ca-valencia">Pantalla d'inici de sessió</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="eo">Ensaluta ekrano</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="es">Pantalla de inicio de sesión</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="eu">Saio-hasteko pantaila</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="fi">Kirjautumisnäkymä</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="fr">Écran de connexion</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="gl">Pantalla de identificación.</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="it">Schermata di accesso</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ka">შესვლის ეკრანი</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ko">로그인 화면</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="nl">Aanmeldscherm</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="nn">Innloggingsbilete</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="pt">Ecrã de autenticação</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="sl">Prijavni zaslon</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="sv">Inloggningsfönster</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ta">நுழைவுத் திரை</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="tr">Oturum açma ekranı</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="uk">Вікно входу</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="x-test">xxLogin screenxx</value>
|
||||
</custom>
|
||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||
<screenshots>
|
||||
@@ -277,77 +322,16 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<screenshot type="default">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/application.png</image>
|
||||
</screenshot>
|
||||
<screenshot x-kde-os="windows">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Timeline.png</image>
|
||||
<caption>Main view with room list, chat, and room information</caption>
|
||||
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
|
||||
<caption xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</caption>
|
||||
<caption xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</caption>
|
||||
<caption xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</caption>
|
||||
<caption xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</caption>
|
||||
<caption xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</caption>
|
||||
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
|
||||
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
|
||||
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
|
||||
<caption xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</caption>
|
||||
<caption xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</caption>
|
||||
<caption xml:lang="nl">Hoofdweergave met lijst met rooms, chat en roominformatie</caption>
|
||||
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</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="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
|
||||
<caption xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</caption>
|
||||
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
|
||||
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
|
||||
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
|
||||
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
|
||||
</screenshot>
|
||||
<screenshot x-kde-os="windows">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</image>
|
||||
<caption>Login screen</caption>
|
||||
<caption xml:lang="ar">شاشة الدخول</caption>
|
||||
<caption xml:lang="ca">Pantalla d'inici de sessió</caption>
|
||||
<caption xml:lang="ca-valencia">Pantalla d'inici de sessió</caption>
|
||||
<caption xml:lang="eo">Ensaluta ekrano</caption>
|
||||
<caption xml:lang="es">Pantalla de inicio de sesión</caption>
|
||||
<caption xml:lang="eu">Saio-hasteko pantaila</caption>
|
||||
<caption xml:lang="fi">Kirjautumisnäkymä</caption>
|
||||
<caption xml:lang="fr">Écran de connexion</caption>
|
||||
<caption xml:lang="gl">Pantalla de identificación.</caption>
|
||||
<caption xml:lang="it">Schermata di accesso</caption>
|
||||
<caption xml:lang="ka">შესვლის ეკრანი</caption>
|
||||
<caption xml:lang="ko">로그인 화면</caption>
|
||||
<caption xml:lang="nl">Aanmeldscherm</caption>
|
||||
<caption xml:lang="nn">Innloggingsbilete</caption>
|
||||
<caption xml:lang="pt">Ecrã de autenticação</caption>
|
||||
<caption xml:lang="sl">Prijavni zaslon</caption>
|
||||
<caption xml:lang="sv">Inloggningsfönster</caption>
|
||||
<caption xml:lang="ta">நுழைவுத் திரை</caption>
|
||||
<caption xml:lang="tr">Oturum açma ekranı</caption>
|
||||
<caption xml:lang="uk">Вікно входу</caption>
|
||||
<caption xml:lang="x-test">xxLogin screenxx</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="23.08.0" date="2023-08-24">
|
||||
<url>https://kde.org/announcements/gear/23.08.0/#neochathttpsappskdeorgneochat</url>
|
||||
<description>
|
||||
<p>Apart from a visual overhaul, NeoChat can now display location events and also a map with the location of all the users currently broadcasting their location using Itineray's Matrix integration. Great for locating where your friends are.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="23.08.0" date="2023-08-24"/>
|
||||
<release version="23.04.3" date="2023-07-06"/>
|
||||
<release version="23.04.2" date="2023-06-08"/>
|
||||
<release version="23.04.1" date="2023-05-11"/>
|
||||
<release version="23.04.0" date="2023-04-20">
|
||||
<url>https://kde.org/announcements/gear/23.04.0/#neochathttpsappskdeorgneochat</url>
|
||||
<description>
|
||||
<p>NeoChat improves its design with tweaks that provide a more compact layout and a simpler menu which works better for the collapsed room list.</p>
|
||||
<p>We have also improved the video controls, added a new command /knock <room-id> to send a knock event to a room, and you can now edit a prior message inline, within the chat pane.</p>
|
||||
<p>Other usability improvements include an overhaul of the keyboard navigation and shortcuts like Ctrl+PgUp/PgDn that allow you to skip from room to room.</p>
|
||||
</description>
|
||||
<artifacts>
|
||||
<artifact type="binary" platform="x86_64-windows-msvc">
|
||||
<location>https://download.kde.org/stable/release-service/23.04.0/windows/neochat-23.04.0-512-windows-cl-msvc2019-x86_64.exe</location>
|
||||
|
||||
711
po/ar/neochat.po
711
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
699
po/az/neochat.po
699
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
685
po/ca/neochat.po
685
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
681
po/cs/neochat.po
681
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
649
po/da/neochat.po
649
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
700
po/de/neochat.po
700
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
707
po/el/neochat.po
707
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
688
po/es/neochat.po
688
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
697
po/eu/neochat.po
697
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
698
po/fi/neochat.po
698
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
698
po/fr/neochat.po
698
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
700
po/hu/neochat.po
700
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
693
po/ia/neochat.po
693
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
697
po/id/neochat.po
697
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
694
po/ie/neochat.po
694
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
698
po/it/neochat.po
698
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
609
po/ja/neochat.po
609
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
703
po/ka/neochat.po
703
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1947
po/ko/neochat.po
1947
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
617
po/lt/neochat.po
617
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
688
po/nl/neochat.po
688
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
317
po/nn/neochat.po
317
po/nn/neochat.po
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: neochat\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
|
||||
"POT-Creation-Date: 2023-08-27 00:46+0000\n"
|
||||
"PO-Revision-Date: 2023-08-24 21:25+0200\n"
|
||||
"POT-Creation-Date: 2023-08-21 00:46+0000\n"
|
||||
"PO-Revision-Date: 2023-08-10 18:43+0200\n"
|
||||
"Last-Translator: Karl Ove Hufthammer <karl@huftis.org>\n"
|
||||
"Language-Team: Norwegian Nynorsk <l10n-no@lister.huftis.org>\n"
|
||||
"Language: nn\n"
|
||||
@@ -20,77 +20,77 @@ msgstr ""
|
||||
"X-Accelerator-Marker: &\n"
|
||||
"X-Text-Markup: kde4\n"
|
||||
|
||||
#: src/controller.cpp:245
|
||||
#: src/controller.cpp:233
|
||||
#, kde-format
|
||||
msgid "Login Failed: Access Token invalid or revoked"
|
||||
msgstr "Feil ved innlogging: Ugyldig eller tilbaketrekt tilgangspollett"
|
||||
|
||||
#: src/controller.cpp:248 src/controller.cpp:253 src/login.cpp:90
|
||||
#: src/controller.cpp:236 src/controller.cpp:241 src/login.cpp:90
|
||||
#, kde-format
|
||||
msgid "Login Failed: %1"
|
||||
msgstr "Feil ved innlogging: %1"
|
||||
|
||||
#: src/controller.cpp:259
|
||||
#: src/controller.cpp:247
|
||||
#, kde-format
|
||||
msgid "Network Error: %1"
|
||||
msgstr "Nettverksfeil: %1"
|
||||
|
||||
#: src/controller.cpp:284
|
||||
#: src/controller.cpp:272
|
||||
#, kde-format
|
||||
msgid "Access token wasn't found"
|
||||
msgstr "Fann ikkje tilgangspollett"
|
||||
|
||||
#: src/controller.cpp:284
|
||||
#: src/controller.cpp:272
|
||||
#, kde-format
|
||||
msgid "Maybe it was deleted?"
|
||||
msgstr "Kanskje han er sletta?"
|
||||
|
||||
#: src/controller.cpp:288
|
||||
#: src/controller.cpp:276
|
||||
#, kde-format
|
||||
msgid "Access to keychain was denied."
|
||||
msgstr "Vart nekta tilgang til nøkkelring."
|
||||
|
||||
#: src/controller.cpp:288
|
||||
#: src/controller.cpp:276
|
||||
#, kde-format
|
||||
msgid "Please allow NeoChat to read the access token"
|
||||
msgstr "Gje NeoChat løyve til å lesa tilgangspolletten"
|
||||
|
||||
#: src/controller.cpp:291
|
||||
#: src/controller.cpp:279
|
||||
#, kde-format
|
||||
msgid "No keychain available."
|
||||
msgstr "Ingen nøkkelring er tilgjengeleg."
|
||||
|
||||
#: src/controller.cpp:291
|
||||
#: src/controller.cpp:279
|
||||
#, kde-format
|
||||
msgid "Please install a keychain, e.g. KWallet or GNOME keyring on Linux"
|
||||
msgstr "Installer ein nøkkelring, for eksempel KWallet eller GNOME Keyring"
|
||||
|
||||
#: src/controller.cpp:294
|
||||
#: src/controller.cpp:282
|
||||
#, kde-format
|
||||
msgid "Unable to read access token"
|
||||
msgstr "Klarte ikkje lesa tilgangspollett"
|
||||
|
||||
#: src/controller.cpp:464
|
||||
#: src/controller.cpp:452
|
||||
#, kde-format
|
||||
msgid "File too large to download."
|
||||
msgstr "Fila er for stor til å kunna lastast ned."
|
||||
|
||||
#: src/controller.cpp:464
|
||||
#: src/controller.cpp:452
|
||||
#, kde-format
|
||||
msgid "Contact your matrix server administrator for support."
|
||||
msgstr "Ta kontakt med administratoren av Matrix-tenaren for brukarstøtte."
|
||||
|
||||
#: src/controller.cpp:503
|
||||
#: src/controller.cpp:491
|
||||
#, kde-format
|
||||
msgid "Room creation failed: %1"
|
||||
msgstr "Feil ved romregistrering: %1"
|
||||
|
||||
#: src/controller.cpp:524
|
||||
#: src/controller.cpp:512
|
||||
#, kde-format
|
||||
msgid "Space creation failed: %1"
|
||||
msgstr "Feil ved registrering av område: %1"
|
||||
|
||||
#: src/controller.cpp:538
|
||||
#: src/controller.cpp:526
|
||||
#, kde-format
|
||||
msgid "The room id you are trying to join is not valid"
|
||||
msgstr "Rom-ID-en du prøver å bruka, er ikkje gyldig"
|
||||
@@ -202,17 +202,17 @@ msgctxt "<version number> (built against <possibly different version number>)"
|
||||
msgid "%1 (built against %2)"
|
||||
msgstr "%1 (bygd mot %2)"
|
||||
|
||||
#: src/main.cpp:334
|
||||
#: src/main.cpp:333
|
||||
#, kde-format
|
||||
msgid "Client for the matrix communication protocol"
|
||||
msgstr "Lynmeldingsklient for Matrix-protokollen"
|
||||
|
||||
#: src/main.cpp:335
|
||||
#: src/main.cpp:334
|
||||
#, kde-format
|
||||
msgid "Supports matrix: url scheme"
|
||||
msgstr "Støttar «matrix:»-adresser"
|
||||
|
||||
#: src/main.cpp:336
|
||||
#: src/main.cpp:335
|
||||
#, kde-format
|
||||
msgid "Ignore all SSL Errors, e.g., unsigned certificates."
|
||||
msgstr "Ignorer alle SSL-feil, for eksempel usignerte sertifikat."
|
||||
@@ -227,312 +227,312 @@ msgstr "Medie-ID-en «%1» følgjer ikkje mønsteret «tenar/medie-ID»"
|
||||
msgid "Image request has been cancelled"
|
||||
msgstr "Biletførespurnaden vart avbroten"
|
||||
|
||||
#: src/models/actionsmodel.cpp:24
|
||||
#: src/models/actionsmodel.cpp:23
|
||||
#, kde-format
|
||||
msgid "Leaving this room."
|
||||
msgstr "Forlèt rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:31 src/models/actionsmodel.cpp:228
|
||||
#: src/models/actionsmodel.cpp:254 src/models/actionsmodel.cpp:284
|
||||
#: src/models/actionsmodel.cpp:30 src/models/actionsmodel.cpp:227
|
||||
#: src/models/actionsmodel.cpp:253 src/models/actionsmodel.cpp:283
|
||||
#, kde-format
|
||||
msgctxt "'<text>' does not look like a room id or alias."
|
||||
msgid "'%1' does not look like a room id or alias."
|
||||
msgstr "«%1» ser ikkje ut til å vera ein rom-ID eller eit alias."
|
||||
|
||||
#: src/models/actionsmodel.cpp:39
|
||||
#: src/models/actionsmodel.cpp:38
|
||||
#, kde-format
|
||||
msgctxt "Leaving room <roomname>."
|
||||
msgid "Leaving room %1."
|
||||
msgstr "Forlèt rom %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:42
|
||||
#: src/models/actionsmodel.cpp:41
|
||||
#, kde-format
|
||||
msgctxt "Room <roomname> not found"
|
||||
msgid "Room %1 not found."
|
||||
msgstr "Fann ikkje rommet %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:50 src/models/actionsmodel.cpp:320
|
||||
#: src/models/actionsmodel.cpp:49 src/models/actionsmodel.cpp:319
|
||||
#, kde-format
|
||||
msgid "No new nickname provided, no changes will happen."
|
||||
msgstr "Nytt kallenamn ikkje oppgjeve. Ingen endringar vert gjort."
|
||||
|
||||
#: src/models/actionsmodel.cpp:65 src/models/actionsmodel.cpp:75
|
||||
#: src/models/actionsmodel.cpp:85 src/models/actionsmodel.cpp:95
|
||||
#: src/models/actionsmodel.cpp:115 src/models/actionsmodel.cpp:135
|
||||
#: src/models/actionsmodel.cpp:146 src/models/actionsmodel.cpp:162
|
||||
#: src/models/actionsmodel.cpp:172 src/models/actionsmodel.cpp:182
|
||||
#: src/models/actionsmodel.cpp:64 src/models/actionsmodel.cpp:74
|
||||
#: src/models/actionsmodel.cpp:84 src/models/actionsmodel.cpp:94
|
||||
#: src/models/actionsmodel.cpp:114 src/models/actionsmodel.cpp:134
|
||||
#: src/models/actionsmodel.cpp:145 src/models/actionsmodel.cpp:161
|
||||
#: src/models/actionsmodel.cpp:171 src/models/actionsmodel.cpp:181
|
||||
msgid "<message>"
|
||||
msgstr "<melding>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:66
|
||||
#: src/models/actionsmodel.cpp:65
|
||||
msgid "Prepends ¯\\_(ツ)_/¯ to a plain-text message"
|
||||
msgstr "Legg til ¯\\_(ツ)_/¯ i starten av ei reintekstmelding"
|
||||
|
||||
#: src/models/actionsmodel.cpp:76
|
||||
#: src/models/actionsmodel.cpp:75
|
||||
msgid "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message"
|
||||
msgstr "Legg til ( ͡° ͜ʖ ͡°) i starten av ei reintekstmelding"
|
||||
|
||||
#: src/models/actionsmodel.cpp:86
|
||||
#: src/models/actionsmodel.cpp:85
|
||||
msgid "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message"
|
||||
msgstr "Legg til (╯°□°)╯︵ ┻━┻ i starten av ei reintekstmelding"
|
||||
|
||||
#: src/models/actionsmodel.cpp:96
|
||||
#: src/models/actionsmodel.cpp:95
|
||||
msgid "Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message"
|
||||
msgstr "Legg til ┬──┬ ノ( ゜-゜ノ) i starten av ei reintekstmelding"
|
||||
|
||||
#: src/models/actionsmodel.cpp:116
|
||||
#: src/models/actionsmodel.cpp:115
|
||||
msgid "Sends the given message colored as a rainbow"
|
||||
msgstr "Sender oppgjeven melding fargelagd som ein regnboge"
|
||||
|
||||
#: src/models/actionsmodel.cpp:136
|
||||
#: src/models/actionsmodel.cpp:135
|
||||
msgid "Sends the given emote colored as a rainbow"
|
||||
msgstr "Sender oppgjeve uttrykk fargelagd som ein regnboge"
|
||||
|
||||
#: src/models/actionsmodel.cpp:147
|
||||
#: src/models/actionsmodel.cpp:146
|
||||
msgid "Sends the given message as plain text"
|
||||
msgstr "Sender oppgjeven melding som reintekst"
|
||||
|
||||
#: src/models/actionsmodel.cpp:163
|
||||
#: src/models/actionsmodel.cpp:162
|
||||
msgid "Sends the given message as a spoiler"
|
||||
msgstr "Sender oppgjeven melding med røpealarm"
|
||||
|
||||
#: src/models/actionsmodel.cpp:173
|
||||
#: src/models/actionsmodel.cpp:172
|
||||
msgid "Sends the given emote"
|
||||
msgstr "Sender oppgjeve uttrykk"
|
||||
|
||||
#: src/models/actionsmodel.cpp:183
|
||||
#: src/models/actionsmodel.cpp:182
|
||||
msgid "Sends the given message as a notice"
|
||||
msgstr "Sender oppgjeven melding som ei varsling"
|
||||
|
||||
#: src/models/actionsmodel.cpp:192 src/models/actionsmodel.cpp:354
|
||||
#: src/models/actionsmodel.cpp:382 src/models/actionsmodel.cpp:432
|
||||
#: src/models/actionsmodel.cpp:470 src/models/actionsmodel.cpp:505
|
||||
#: src/models/actionsmodel.cpp:191 src/models/actionsmodel.cpp:353
|
||||
#: src/models/actionsmodel.cpp:381 src/models/actionsmodel.cpp:431
|
||||
#: src/models/actionsmodel.cpp:469 src/models/actionsmodel.cpp:504
|
||||
#, kde-format
|
||||
msgctxt "'<text>' does not look like a matrix id."
|
||||
msgid "'%1' does not look like a matrix id."
|
||||
msgstr "«%1» ser ikkje ut til å vera ein Matrix-ID."
|
||||
|
||||
#: src/models/actionsmodel.cpp:197
|
||||
#: src/models/actionsmodel.cpp:196
|
||||
#, kde-format
|
||||
msgctxt "<user> is already invited to this room."
|
||||
msgid "%1 is already invited to this room."
|
||||
msgstr "%1 er alt invitert til dette rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:201
|
||||
#: src/models/actionsmodel.cpp:200
|
||||
#, kde-format
|
||||
msgctxt "<user> is banned from this room."
|
||||
msgid "%1 is banned from this room."
|
||||
msgstr "%1 er utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:205
|
||||
#: src/models/actionsmodel.cpp:204
|
||||
#, kde-format
|
||||
msgid "You are already in this room."
|
||||
msgstr "Du er alt i dette rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:209
|
||||
#: src/models/actionsmodel.cpp:208
|
||||
#, kde-format
|
||||
msgctxt "<user> is already in this room."
|
||||
msgid "%1 is already in this room."
|
||||
msgstr "%1 er alt i dette rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:213
|
||||
#: src/models/actionsmodel.cpp:212
|
||||
#, kde-format
|
||||
msgctxt "<username> was invited into this room"
|
||||
msgid "%1 was invited into this room"
|
||||
msgstr "%1 vart invitert til rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:218 src/models/actionsmodel.cpp:372
|
||||
#: src/models/actionsmodel.cpp:400 src/models/actionsmodel.cpp:493
|
||||
#: src/models/actionsmodel.cpp:217 src/models/actionsmodel.cpp:371
|
||||
#: src/models/actionsmodel.cpp:399 src/models/actionsmodel.cpp:492
|
||||
msgid "<user id>"
|
||||
msgstr "<brukar-ID>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:219
|
||||
#: src/models/actionsmodel.cpp:218
|
||||
msgid "Invites the user to this room"
|
||||
msgstr "Inviterer brukaren til rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:236 src/models/actionsmodel.cpp:291
|
||||
#: src/models/actionsmodel.cpp:235 src/models/actionsmodel.cpp:290
|
||||
#, kde-format
|
||||
msgctxt "Joining room <roomname>."
|
||||
msgid "Joining room %1."
|
||||
msgstr "Vert med i rommet %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:242 src/models/actionsmodel.cpp:297
|
||||
#: src/models/actionsmodel.cpp:241 src/models/actionsmodel.cpp:296
|
||||
msgid "<room alias or id>"
|
||||
msgstr "<rom-alias eller -ID>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:243 src/models/actionsmodel.cpp:298
|
||||
#: src/models/actionsmodel.cpp:242 src/models/actionsmodel.cpp:297
|
||||
msgid "Joins the given room"
|
||||
msgstr "Vert med i rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:262
|
||||
#: src/models/actionsmodel.cpp:261
|
||||
#, kde-format
|
||||
msgctxt "Knocking room <roomname>."
|
||||
msgid "Knocking room %1."
|
||||
msgstr "Bankar på rommet %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:274
|
||||
#: src/models/actionsmodel.cpp:273
|
||||
msgid "<room alias or id> [<reason>]"
|
||||
msgstr "<rom-alias eller -id> [<grunngjeving>]"
|
||||
|
||||
#: src/models/actionsmodel.cpp:275
|
||||
#: src/models/actionsmodel.cpp:274
|
||||
msgid "Requests to join the given room"
|
||||
msgstr "Førespurnad om å få verta med i rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:288
|
||||
#: src/models/actionsmodel.cpp:287
|
||||
#, kde-format
|
||||
msgctxt "You are already in room <roomname>."
|
||||
msgid "You are already in room %1."
|
||||
msgstr "Du er alt med i rom %1."
|
||||
|
||||
#: src/models/actionsmodel.cpp:305 src/models/actionsmodel.cpp:313
|
||||
#: src/models/actionsmodel.cpp:304 src/models/actionsmodel.cpp:312
|
||||
msgid "[<room alias or id>]"
|
||||
msgstr "[<rom-alias eller -id>]"
|
||||
|
||||
#: src/models/actionsmodel.cpp:306 src/models/actionsmodel.cpp:314
|
||||
#: src/models/actionsmodel.cpp:305 src/models/actionsmodel.cpp:313
|
||||
msgid "Leaves the given room or this room, if there is none given"
|
||||
msgstr "Forlèt valt eller (viss romnamn ikkje er oppgjeve) gjeldande rom"
|
||||
|
||||
#: src/models/actionsmodel.cpp:328 src/models/actionsmodel.cpp:336
|
||||
#: src/models/actionsmodel.cpp:344
|
||||
#: src/models/actionsmodel.cpp:327 src/models/actionsmodel.cpp:335
|
||||
#: src/models/actionsmodel.cpp:343
|
||||
msgid "<display name>"
|
||||
msgstr "<visingsnamn>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:329
|
||||
#: src/models/actionsmodel.cpp:328
|
||||
msgid "Changes your global display name"
|
||||
msgstr "Byter ditt globale visingsnamn"
|
||||
|
||||
#: src/models/actionsmodel.cpp:337 src/models/actionsmodel.cpp:345
|
||||
#: src/models/actionsmodel.cpp:336 src/models/actionsmodel.cpp:344
|
||||
msgid "Changes your display name in this room"
|
||||
msgstr "Byter visingsnamnet ditt i dette rommet"
|
||||
|
||||
#: src/models/actionsmodel.cpp:359
|
||||
#: src/models/actionsmodel.cpp:358
|
||||
#, kde-format
|
||||
msgctxt "<username> is already ignored."
|
||||
msgid "%1 is already ignored."
|
||||
msgstr "%1 er ignorert frå før."
|
||||
|
||||
#: src/models/actionsmodel.cpp:364
|
||||
#: src/models/actionsmodel.cpp:363
|
||||
#, kde-format
|
||||
msgctxt "<username> is now ignored"
|
||||
msgid "%1 is now ignored."
|
||||
msgstr "%1 er no ignorert."
|
||||
|
||||
#: src/models/actionsmodel.cpp:366 src/models/actionsmodel.cpp:394
|
||||
#: src/models/actionsmodel.cpp:365 src/models/actionsmodel.cpp:393
|
||||
#, kde-format
|
||||
msgctxt "<username> is not a known user"
|
||||
msgid "%1 is not a known user."
|
||||
msgstr "%1 er ein ukjend brukar."
|
||||
|
||||
#: src/models/actionsmodel.cpp:373
|
||||
#: src/models/actionsmodel.cpp:372
|
||||
msgid "Ignores the given user"
|
||||
msgstr "Ignorerer brukaren"
|
||||
|
||||
#: src/models/actionsmodel.cpp:388
|
||||
#: src/models/actionsmodel.cpp:387
|
||||
#, kde-format
|
||||
msgctxt "<username> is not ignored."
|
||||
msgid "%1 is not ignored."
|
||||
msgstr "%1 er ikkje ignorert."
|
||||
|
||||
#: src/models/actionsmodel.cpp:392
|
||||
#: src/models/actionsmodel.cpp:391
|
||||
#, kde-format
|
||||
msgctxt "<username> is no longer ignored."
|
||||
msgid "%1 is no longer ignored."
|
||||
msgstr "%1 er ikkje lenger ignorert."
|
||||
|
||||
#: src/models/actionsmodel.cpp:401
|
||||
#: src/models/actionsmodel.cpp:400
|
||||
msgid "Unignores the given user"
|
||||
msgstr "Avignorerer brukaren"
|
||||
|
||||
#: src/models/actionsmodel.cpp:421
|
||||
#: src/models/actionsmodel.cpp:420
|
||||
msgid "<reaction text>"
|
||||
msgstr "<reaksjonstekst>"
|
||||
|
||||
#: src/models/actionsmodel.cpp:422
|
||||
#: src/models/actionsmodel.cpp:421
|
||||
msgid "React to the message with the given text"
|
||||
msgstr "Reager på meldinga med ein tekst"
|
||||
|
||||
#: src/models/actionsmodel.cpp:437
|
||||
#: src/models/actionsmodel.cpp:436
|
||||
#, kde-format
|
||||
msgctxt "<user> is already banned from this room."
|
||||
msgid "%1 is already banned from this room."
|
||||
msgstr "%1 er alt utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:445
|
||||
#: src/models/actionsmodel.cpp:444
|
||||
#, kde-format
|
||||
msgid "You are not allowed to ban users from this room."
|
||||
msgstr "Du har ikkje løyve til å utestengja brukarar frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:451
|
||||
#: src/models/actionsmodel.cpp:450
|
||||
#, kde-format
|
||||
msgctxt "You are not allowed to ban <username> from this room."
|
||||
msgid "You are not allowed to ban %1 from this room."
|
||||
msgstr "Du har ikkje løyve til å utestengja %1 frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:455
|
||||
#: src/models/actionsmodel.cpp:454
|
||||
#, kde-format
|
||||
msgctxt "<username> was banned from this room."
|
||||
msgid "%1 was banned from this room."
|
||||
msgstr "%1 vart utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:460 src/models/actionsmodel.cpp:537
|
||||
#: src/models/actionsmodel.cpp:459 src/models/actionsmodel.cpp:536
|
||||
msgid "<user id> [<reason>]"
|
||||
msgstr "<brukar-ID> [<grunngjeving>]"
|
||||
|
||||
#: src/models/actionsmodel.cpp:461
|
||||
#: src/models/actionsmodel.cpp:460
|
||||
msgid "Bans the given user"
|
||||
msgstr "Utestengjer brukaren"
|
||||
|
||||
#: src/models/actionsmodel.cpp:478
|
||||
#: src/models/actionsmodel.cpp:477
|
||||
#, kde-format
|
||||
msgid "You are not allowed to unban users from this room."
|
||||
msgstr "Du har ikkje løyve til å oppheva utestenging frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:483
|
||||
#: src/models/actionsmodel.cpp:482
|
||||
#, kde-format
|
||||
msgctxt "<user> is not banned from this room."
|
||||
msgid "%1 is not banned from this room."
|
||||
msgstr "%1 er ikkje utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:487
|
||||
#: src/models/actionsmodel.cpp:486
|
||||
#, kde-format
|
||||
msgctxt "<username> was unbanned from this room."
|
||||
msgid "%1 was unbanned from this room."
|
||||
msgstr "%1 er ikkje lenger utestengd frå rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:494
|
||||
#: src/models/actionsmodel.cpp:493
|
||||
msgid "Removes the ban of the given user"
|
||||
msgstr "Opphevar utestenging av brukaren"
|
||||
|
||||
#: src/models/actionsmodel.cpp:509
|
||||
#: src/models/actionsmodel.cpp:508
|
||||
#, kde-format
|
||||
msgid "You cannot kick yourself from the room."
|
||||
msgstr "Du kan ikkje kasta deg sjølv ut av rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:513
|
||||
#: src/models/actionsmodel.cpp:512
|
||||
#, kde-format
|
||||
msgctxt "<username> is not in this room"
|
||||
msgid "%1 is not in this room."
|
||||
msgstr "%1 er ikkje med i rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:522
|
||||
#: src/models/actionsmodel.cpp:521
|
||||
#, kde-format
|
||||
msgid "You are not allowed to kick users from this room."
|
||||
msgstr "Du har ikkje løyve til å kasta brukarar ut av rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:528
|
||||
#: src/models/actionsmodel.cpp:527
|
||||
#, kde-format
|
||||
msgctxt "You are not allowed to kick <username> from this room"
|
||||
msgid "You are not allowed to kick %1 from this room."
|
||||
msgstr "Du har ikkje løyve til å kasta %1 ut av rommet."
|
||||
|
||||
#: src/models/actionsmodel.cpp:532
|
||||
#: src/models/actionsmodel.cpp:531
|
||||
#, kde-format
|
||||
msgctxt "<username> was kicked from this room."
|
||||
msgid "%1 was kicked from this room."
|
||||
msgstr "%1 vart kasta ut av rommet."
|
||||
|
||||
# Er dette noko anna enn å kasta brukaren ut?
|
||||
#: src/models/actionsmodel.cpp:538
|
||||
#: src/models/actionsmodel.cpp:537
|
||||
msgid "Removes the user from the room"
|
||||
msgstr "Fjernar brukaren frå rommet"
|
||||
|
||||
@@ -636,13 +636,13 @@ msgctxt "'Custom' is a category of emoji"
|
||||
msgid "Custom"
|
||||
msgstr "Tilpassa"
|
||||
|
||||
#: src/models/imagepacksmodel.cpp:87
|
||||
#: src/models/imagepacksmodel.cpp:86
|
||||
#, kde-format
|
||||
msgctxt "As in 'The user's own Stickers'"
|
||||
msgid "Own Stickers"
|
||||
msgstr "Eigne klistremerke"
|
||||
|
||||
#: src/models/imagepacksmodel.cpp:87
|
||||
#: src/models/imagepacksmodel.cpp:86
|
||||
#, kde-format
|
||||
msgctxt "As in 'The user's own emojis"
|
||||
msgid "Own Emojis"
|
||||
@@ -1079,12 +1079,12 @@ msgstr "oppdaterte tilstanden"
|
||||
msgid "started a poll"
|
||||
msgstr "starta ei avstemming"
|
||||
|
||||
#: src/neochatroom.cpp:1631 src/neochatroom.cpp:1632
|
||||
#: src/neochatroom.cpp:1627 src/neochatroom.cpp:1628
|
||||
#, kde-format
|
||||
msgid "Report sent successfully."
|
||||
msgstr "Rapporten er no send."
|
||||
|
||||
#: src/neochatroom.cpp:1928 src/neochatroom.cpp:1936
|
||||
#: src/neochatroom.cpp:1924 src/neochatroom.cpp:1932
|
||||
#, kde-format
|
||||
msgctxt "'Lat' and 'Lon' as in Latitude and Longitude"
|
||||
msgid "Lat: %1, Lon: %2"
|
||||
@@ -1095,7 +1095,7 @@ msgstr "Breiddegr.: %1 – lengdegr.: %2"
|
||||
msgid "Encrypted Message"
|
||||
msgstr "Kryptert melding"
|
||||
|
||||
#: src/notificationsmanager.cpp:201 src/qml/main.qml:257
|
||||
#: src/notificationsmanager.cpp:201 src/qml/main.qml:253
|
||||
#, kde-format
|
||||
msgid "%1: %2"
|
||||
msgstr "%1: %2"
|
||||
@@ -1362,8 +1362,7 @@ msgstr "Avvis"
|
||||
msgid "Accept"
|
||||
msgstr "Godta"
|
||||
|
||||
#: src/qml/Component/LocationPage.qml:17
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:116
|
||||
#: src/qml/Component/LocationPage.qml:17 src/qml/Panel/RoomDrawer.qml:200
|
||||
#, kde-format
|
||||
msgctxt "Locations on a map"
|
||||
msgid "Locations"
|
||||
@@ -1609,22 +1608,22 @@ msgstr "Lydstyrke"
|
||||
msgid "Maximize"
|
||||
msgstr "Maksimer"
|
||||
|
||||
#: src/qml/Component/TimelineView.qml:160
|
||||
#: src/qml/Component/TimelineView.qml:153
|
||||
#, kde-format
|
||||
msgid "Jump to first unread message"
|
||||
msgstr "Gå til første ulesne melding"
|
||||
|
||||
#: src/qml/Component/TimelineView.qml:183
|
||||
#: src/qml/Component/TimelineView.qml:176
|
||||
#, kde-format
|
||||
msgid "Jump to latest message"
|
||||
msgstr "Gå til nyaste melding"
|
||||
|
||||
#: src/qml/Component/TimelineView.qml:209
|
||||
#: src/qml/Component/TimelineView.qml:202
|
||||
#, kde-format
|
||||
msgid "Drag items here to share them"
|
||||
msgstr "Dra element her for å dela dei"
|
||||
|
||||
#: src/qml/Component/TimelineView.qml:235
|
||||
#: src/qml/Component/TimelineView.qml:228
|
||||
#, kde-format
|
||||
msgctxt "Message displayed when some users are typing"
|
||||
msgid "%2 is typing"
|
||||
@@ -1957,80 +1956,80 @@ msgctxt "@title:menu Account detail dialog"
|
||||
msgid "Account detail"
|
||||
msgstr "Kontodetaljar"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:81
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:80
|
||||
#, kde-format
|
||||
msgid "Unignore this user"
|
||||
msgstr "Avignorer brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:81
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:80
|
||||
#, kde-format
|
||||
msgid "Ignore this user"
|
||||
msgstr "Ignorer brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:93
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:92
|
||||
#, kde-format
|
||||
msgid "Kick this user"
|
||||
msgstr "Kast ut brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:106
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:105
|
||||
#, kde-format
|
||||
msgid "Invite this user"
|
||||
msgstr "Inviter brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:118
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:117
|
||||
#, kde-format
|
||||
msgid "Ban this user"
|
||||
msgstr "Utesteng brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:123
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:122
|
||||
#, kde-format
|
||||
msgctxt "@title"
|
||||
msgid "Ban User"
|
||||
msgstr "Utesteng brukar"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:134
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:133
|
||||
#, kde-format
|
||||
msgid "Unban this user"
|
||||
msgstr "Opphev utestenging av brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:146
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:145
|
||||
#, kde-format
|
||||
msgid "Set user power level"
|
||||
msgstr "Vel maktnivå for brukar"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:170
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:169
|
||||
#, kde-format
|
||||
msgid "Remove recent messages by this user"
|
||||
msgstr "Fjern nylege meldingar frå brukaren"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:175
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:174
|
||||
#, kde-format
|
||||
msgctxt "@title"
|
||||
msgid "Remove Messages"
|
||||
msgstr "Fjern meldingar"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:185
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:184
|
||||
#, kde-format
|
||||
msgid "Open a private chat"
|
||||
msgstr "Start privat prat"
|
||||
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:195
|
||||
#: src/qml/Dialog/UserDetailDialog.qml:194
|
||||
#, kde-format
|
||||
msgid "Copy link"
|
||||
msgstr "Kopier lenkje"
|
||||
|
||||
#: src/qml/main.qml:297
|
||||
#: src/qml/main.qml:293
|
||||
#, kde-format
|
||||
msgctxt "@title:window"
|
||||
msgid "Session Verification"
|
||||
msgstr "Øktstadfesting"
|
||||
|
||||
#: src/qml/main.qml:309
|
||||
#: src/qml/main.qml:305
|
||||
#, kde-format
|
||||
msgid "User consent"
|
||||
msgstr "Brukarsamtykke"
|
||||
|
||||
#: src/qml/main.qml:314
|
||||
#: src/qml/main.qml:310
|
||||
#, kde-format
|
||||
msgid ""
|
||||
"Your homeserver requires you to agree to its terms and conditions before "
|
||||
@@ -2039,17 +2038,17 @@ msgstr ""
|
||||
"Heimetenaren krev at du godtek brukarvilkåra før du kan ta han i bruk. Trykk "
|
||||
"på knappen nedanfor for å lesa vilkåra."
|
||||
|
||||
#: src/qml/main.qml:319
|
||||
#: src/qml/main.qml:315
|
||||
#, kde-format
|
||||
msgid "Open"
|
||||
msgstr "Opna"
|
||||
|
||||
#: src/qml/main.qml:354
|
||||
#: src/qml/main.qml:350
|
||||
#, kde-format
|
||||
msgid "Start a chat"
|
||||
msgstr "Start prat"
|
||||
|
||||
#: src/qml/main.qml:356
|
||||
#: src/qml/main.qml:352
|
||||
#, kde-format
|
||||
msgid "Do you want to start a chat with %1?"
|
||||
msgstr "Vil du starta ein prat med %1?"
|
||||
@@ -2227,25 +2226,25 @@ msgstr "Feil ved deling"
|
||||
msgid "Shared url for image is <a href='%1'>%1</a>"
|
||||
msgstr "Delt adresse for biletet er <a href='%1'>%1</a>"
|
||||
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:18
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:16
|
||||
#, kde-format
|
||||
msgid "Ban User"
|
||||
msgstr "Utesteng brukar"
|
||||
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:22
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:20
|
||||
#, kde-format
|
||||
msgid "Reason for banning this user"
|
||||
msgstr "Grunngjeving for utestenging av brukaren"
|
||||
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:34
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:32
|
||||
#, kde-format
|
||||
msgctxt "@action:button 'Ban' as in 'Ban this user'"
|
||||
msgid "Ban"
|
||||
msgstr "Utesteng"
|
||||
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:43
|
||||
#: src/qml/Menu/Timeline/BanSheet.qml:41
|
||||
#: src/qml/Menu/Timeline/RemoveSheet.qml:49
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:43 src/qml/Page/InviteUserPage.qml:24
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:41 src/qml/Page/InviteUserPage.qml:24
|
||||
#, kde-format
|
||||
msgctxt "@action"
|
||||
msgid "Cancel"
|
||||
@@ -2282,7 +2281,7 @@ msgstr "Fjern melding"
|
||||
|
||||
#: src/qml/Menu/Timeline/FileDelegateContextMenu.qml:83
|
||||
#: src/qml/Menu/Timeline/MessageDelegateContextMenu.qml:59
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:34
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:32
|
||||
#, kde-format
|
||||
msgctxt ""
|
||||
"@action:button 'Report' as in 'Report this event to the administrators'"
|
||||
@@ -2349,17 +2348,17 @@ msgctxt "@action:button 'Remove' as in 'Remove this message'"
|
||||
msgid "Remove"
|
||||
msgstr "Fjern"
|
||||
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:18
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:16
|
||||
#, kde-format
|
||||
msgid "Report Message"
|
||||
msgstr "Rapporter melding"
|
||||
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:22
|
||||
#: src/qml/Menu/Timeline/ReportSheet.qml:20
|
||||
#, kde-format
|
||||
msgid "Reason for reporting this message"
|
||||
msgstr "Grunngjeving for rapportering av meldinga"
|
||||
|
||||
#: src/qml/Page/DevtoolsPage.qml:17 src/qml/RoomDrawer/RoomInformation.qml:75
|
||||
#: src/qml/Page/DevtoolsPage.qml:17 src/qml/Panel/RoomDrawer.qml:159
|
||||
#, kde-format
|
||||
msgid "Developer Tools"
|
||||
msgstr "Utviklarverktøy"
|
||||
@@ -2520,7 +2519,7 @@ msgstr "Vart med"
|
||||
#, kde-format
|
||||
msgctxt "@info:label"
|
||||
msgid "No rooms found"
|
||||
msgstr "Fann ingen rom"
|
||||
msgstr ""
|
||||
|
||||
#: src/qml/Page/RoomList/AccountMenu.qml:18
|
||||
#: src/qml/Page/RoomList/UserInfo.qml:175
|
||||
@@ -2635,9 +2634,7 @@ msgstr "Av"
|
||||
|
||||
#: src/qml/Page/RoomList/ContextMenu.qml:117
|
||||
#: src/qml/Page/RoomList/ContextMenu.qml:119
|
||||
#: src/qml/Page/RoomList/ContextMenu.qml:186
|
||||
#: src/qml/RoomDrawer/RoomDrawer.qml:98
|
||||
#: src/qml/RoomDrawer/RoomDrawerPage.qml:37
|
||||
#: src/qml/Page/RoomList/ContextMenu.qml:186 src/qml/Panel/RoomDrawer.qml:105
|
||||
#, kde-format
|
||||
msgid "Room Settings"
|
||||
msgstr "Romval"
|
||||
@@ -2649,7 +2646,7 @@ msgid "Leave Room"
|
||||
msgstr "Forlat rommet"
|
||||
|
||||
#: src/qml/Page/RoomList/ExploreComponent.qml:19
|
||||
#: src/qml/Page/RoomList/Page.qml:153
|
||||
#: src/qml/Page/RoomList/Page.qml:154
|
||||
#, kde-format
|
||||
msgid "Explore rooms"
|
||||
msgstr "Utforsk rom"
|
||||
@@ -2660,28 +2657,28 @@ msgstr "Utforsk rom"
|
||||
msgid "Create rooms and chats"
|
||||
msgstr "Opprett rom og diskusjonar"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:150
|
||||
#: src/qml/Page/RoomList/Page.qml:151
|
||||
#, kde-format
|
||||
msgid "No rooms found"
|
||||
msgstr "Fann ingen rom"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:150
|
||||
#: src/qml/Page/RoomList/Page.qml:151
|
||||
#, kde-format
|
||||
msgid "Join some rooms to get started"
|
||||
msgstr "Start ved å verta med i nokre rom"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:153
|
||||
#: src/qml/Page/RoomList/Page.qml:154
|
||||
#, kde-format
|
||||
msgid "Search in room directory"
|
||||
msgstr "Søk i romkatalogen"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:195
|
||||
#: src/qml/Page/RoomList/Page.qml:196
|
||||
#, kde-format
|
||||
msgctxt "Collapse <section name>"
|
||||
msgid "Collapse %1"
|
||||
msgstr "Fald saman %1"
|
||||
|
||||
#: src/qml/Page/RoomList/Page.qml:195
|
||||
#: src/qml/Page/RoomList/Page.qml:196
|
||||
#, kde-format
|
||||
msgctxt "Expand <section name"
|
||||
msgid "Expand %1"
|
||||
@@ -2753,7 +2750,7 @@ msgstr "Byt brukar"
|
||||
msgid "Open Settings"
|
||||
msgstr "Opna innstillingar"
|
||||
|
||||
#: src/qml/Page/RoomPage.qml:50
|
||||
#: src/qml/Page/RoomPage.qml:42
|
||||
#, kde-format
|
||||
msgid "NeoChat is offline. Please check your network connection."
|
||||
msgstr "NeoChat er fråkopla. Sjå til at du er kopla til nettet."
|
||||
@@ -2794,103 +2791,97 @@ msgstr "Start ny prat"
|
||||
msgid "Welcome to Matrix"
|
||||
msgstr "Velkommen til Matrix"
|
||||
|
||||
#: src/qml/RoomDrawer/GroupChatDrawerHeader.qml:62
|
||||
#: src/qml/Panel/GroupChatDrawerHeader.qml:62
|
||||
#, kde-format
|
||||
msgid "No name"
|
||||
msgstr "Namnlaus"
|
||||
|
||||
#: src/qml/RoomDrawer/GroupChatDrawerHeader.qml:71
|
||||
#: src/qml/Panel/GroupChatDrawerHeader.qml:71
|
||||
#, kde-format
|
||||
msgid "No Canonical Alias"
|
||||
msgstr "Manglar kanonisk alias"
|
||||
|
||||
#: src/qml/RoomDrawer/GroupChatDrawerHeader.qml:81
|
||||
#: src/qml/Panel/GroupChatDrawerHeader.qml:81
|
||||
#, kde-format
|
||||
msgid "No Topic"
|
||||
msgstr "Manglar emne"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomDrawer.qml:88
|
||||
#: src/qml/Panel/RoomDrawer.qml:95
|
||||
#, kde-format
|
||||
msgid "Room information"
|
||||
msgstr "Rominformasjon"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomDrawer.qml:95
|
||||
#: src/qml/Panel/RoomDrawer.qml:102
|
||||
#, kde-format
|
||||
msgid "Room settings"
|
||||
msgstr "Romval"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:38
|
||||
#, kde-format
|
||||
msgctxt "@action:title"
|
||||
msgid "Room information"
|
||||
msgstr ""
|
||||
|
||||
# Er ulike handlingar ein kan gjera, for eksempel gjera rommet til favoritt. «Handlingar» fungerer derfor betre enn «Val» eller «Alternativ».
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:59
|
||||
#: src/qml/Panel/RoomDrawer.qml:143
|
||||
#, kde-format
|
||||
msgid "Options"
|
||||
msgstr "Handlingar"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:69
|
||||
#: src/qml/Panel/RoomDrawer.qml:153
|
||||
#, kde-format
|
||||
msgid "Open developer tools"
|
||||
msgstr "Opna utviklarverktøy"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:83
|
||||
#: src/qml/Panel/RoomDrawer.qml:167
|
||||
#, kde-format
|
||||
msgid "Search in this room"
|
||||
msgstr "Søk i rommet"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:91
|
||||
#: src/qml/Panel/RoomDrawer.qml:175
|
||||
#, kde-format
|
||||
msgctxt "@action:title"
|
||||
msgid "Search"
|
||||
msgstr "Søk"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:100
|
||||
#: src/qml/Panel/RoomDrawer.qml:184
|
||||
#, kde-format
|
||||
msgid "Remove room from favorites"
|
||||
msgstr "Fjern rommet frå favorittar"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:100
|
||||
#: src/qml/Panel/RoomDrawer.qml:184
|
||||
#, kde-format
|
||||
msgid "Make room favorite"
|
||||
msgstr "Gjer rommet til favoritt"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:111
|
||||
#: src/qml/Panel/RoomDrawer.qml:195
|
||||
#, kde-format
|
||||
msgid "Show locations for this room"
|
||||
msgstr "Vis posisjonar i rommet"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:123
|
||||
#: src/qml/Panel/RoomDrawer.qml:207
|
||||
#, kde-format
|
||||
msgid "Members"
|
||||
msgstr "Medlemmar"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:134
|
||||
#: src/qml/Panel/RoomDrawer.qml:218
|
||||
#, kde-format
|
||||
msgid "Search user in room"
|
||||
msgstr "Søk etter brukarar i rommet"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:147
|
||||
#: src/qml/Panel/RoomDrawer.qml:231
|
||||
#, kde-format
|
||||
msgctxt "@title"
|
||||
msgid "Invite a User"
|
||||
msgstr "Inviter ein brukar"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:150
|
||||
#: src/qml/Panel/RoomDrawer.qml:234
|
||||
#, kde-format
|
||||
msgid "Invite user to room"
|
||||
msgstr "Inviter brukar til rommet"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:157
|
||||
#: src/qml/Panel/RoomDrawer.qml:241
|
||||
#, kde-format
|
||||
msgid "%1 member"
|
||||
msgid_plural "%1 members"
|
||||
msgstr[0] "%1 medlem"
|
||||
msgstr[1] "%1 medlemmar"
|
||||
|
||||
#: src/qml/RoomDrawer/RoomInformation.qml:157
|
||||
#: src/qml/Panel/RoomDrawer.qml:241
|
||||
#, kde-format
|
||||
msgid "No member count"
|
||||
msgstr "Manglar medlemstal"
|
||||
|
||||
698
po/pa/neochat.po
698
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
704
po/pl/neochat.po
704
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
697
po/pt/neochat.po
697
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
704
po/ru/neochat.po
704
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
711
po/sk/neochat.po
711
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
697
po/sl/neochat.po
697
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
690
po/sv/neochat.po
690
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
695
po/ta/neochat.po
695
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
689
po/tr/neochat.po
689
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
696
po/uk/neochat.po
696
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -74,6 +74,8 @@ add_library(neochat STATIC
|
||||
blurhash.h
|
||||
blurhashimageprovider.cpp
|
||||
blurhashimageprovider.h
|
||||
models/collapsestateproxymodel.cpp
|
||||
models/collapsestateproxymodel.h
|
||||
models/mediamessagefiltermodel.cpp
|
||||
models/mediamessagefiltermodel.h
|
||||
urlhelper.cpp
|
||||
@@ -123,9 +125,6 @@ add_library(neochat STATIC
|
||||
events/pollevent.cpp
|
||||
pollhandler.cpp
|
||||
utils.h
|
||||
registration.cpp
|
||||
neochatconnection.cpp
|
||||
neochatconnection.h
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(neochat
|
||||
@@ -142,11 +141,6 @@ add_executable(neochat-app
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/res.generated.qrc
|
||||
)
|
||||
|
||||
if(TARGET Qt::WebView)
|
||||
target_link_libraries(neochat-app PUBLIC Qt::WebView)
|
||||
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
|
||||
|
||||
target_link_libraries(neochat-app PRIVATE
|
||||
@@ -183,7 +177,6 @@ endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient${QUOTIENT_SUFFIX} cmark::cmark QCoro::Core)
|
||||
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QGuiApplication>
|
||||
#include <QImageReader>
|
||||
#include <QNetworkProxy>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QQuickWindow>
|
||||
@@ -32,8 +33,10 @@
|
||||
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
#include <Quotient/csapi/logout.h>
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/csapi/profile.h>
|
||||
#include <Quotient/eventstats.h>
|
||||
#include <Quotient/jobs/downloadfilejob.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
@@ -102,8 +105,8 @@ Controller::Controller(QObject *parent)
|
||||
static int oldAccountCount = 0;
|
||||
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
|
||||
if (m_accountRegistry.size() > oldAccountCount) {
|
||||
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
|
||||
connect(connection, &NeoChatConnection::syncDone, this, [connection]() {
|
||||
auto connection = m_accountRegistry.accounts()[m_accountRegistry.size() - 1];
|
||||
connect(connection, &Connection::syncDone, this, [connection]() {
|
||||
NotificationsManager::instance().handleNotifications(connection);
|
||||
});
|
||||
}
|
||||
@@ -138,7 +141,38 @@ void Controller::toggleWindow()
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::addConnection(NeoChatConnection *c)
|
||||
void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
{
|
||||
if (!conn) {
|
||||
qCritical() << "Attempt to logout null connection";
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsGroup("Accounts"_ls).remove(conn->userId());
|
||||
|
||||
QKeychain::DeletePasswordJob job(qAppName());
|
||||
job.setAutoDelete(true);
|
||||
job.setKey(conn->userId());
|
||||
QEventLoop loop;
|
||||
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||
job.start();
|
||||
loop.exec();
|
||||
|
||||
if (m_accountRegistry.count() > 1) {
|
||||
// Only set the connection if the the account being logged out is currently active
|
||||
if (conn == activeConnection()) {
|
||||
setActiveConnection(m_accountRegistry.accounts()[0]);
|
||||
}
|
||||
} else {
|
||||
setActiveConnection(nullptr);
|
||||
}
|
||||
if (!serverSideLogout) {
|
||||
return;
|
||||
}
|
||||
conn->logout();
|
||||
}
|
||||
|
||||
void Controller::addConnection(Connection *c)
|
||||
{
|
||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
|
||||
|
||||
@@ -146,17 +180,17 @@ void Controller::addConnection(NeoChatConnection *c)
|
||||
|
||||
c->setLazyLoading(true);
|
||||
|
||||
connect(c, &NeoChatConnection::syncDone, this, [this, c] {
|
||||
connect(c, &Connection::syncDone, this, [this, c] {
|
||||
Q_EMIT syncDone();
|
||||
|
||||
c->sync(30000);
|
||||
c->saveState();
|
||||
});
|
||||
connect(c, &NeoChatConnection::loggedOut, this, [this, c] {
|
||||
connect(c, &Connection::loggedOut, this, [this, c] {
|
||||
dropConnection(c);
|
||||
});
|
||||
|
||||
connect(c, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
|
||||
connect(c, &Connection::requestFailed, this, [this](BaseJob *job) {
|
||||
if (job->error() == BaseJob::UserConsentRequired) {
|
||||
Q_EMIT userConsentRequired(job->errorUrl());
|
||||
}
|
||||
@@ -168,7 +202,7 @@ void Controller::addConnection(NeoChatConnection *c)
|
||||
Q_EMIT accountCountChanged();
|
||||
}
|
||||
|
||||
void Controller::dropConnection(NeoChatConnection *c)
|
||||
void Controller::dropConnection(Connection *c)
|
||||
{
|
||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
|
||||
|
||||
@@ -197,19 +231,19 @@ void Controller::invokeLogin()
|
||||
return;
|
||||
}
|
||||
|
||||
auto connection = new NeoChatConnection(account.homeserver());
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, id] {
|
||||
auto connection = new Connection(account.homeserver());
|
||||
connect(connection, &Connection::connected, this, [this, connection, id] {
|
||||
connection->loadState();
|
||||
addConnection(connection);
|
||||
if (connection->userId() == id) {
|
||||
setActiveConnection(connection);
|
||||
connectSingleShot(connection, &NeoChatConnection::syncDone, this, &Controller::initiated);
|
||||
connectSingleShot(connection, &Connection::syncDone, this, &Controller::initiated);
|
||||
}
|
||||
});
|
||||
connect(connection, &NeoChatConnection::loginError, this, [this, connection](const QString &error, const QString &) {
|
||||
connect(connection, &Connection::loginError, this, [this, connection](const QString &error, const QString &) {
|
||||
if (error == "Unrecognised access token"_ls) {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
|
||||
connection->logout(false);
|
||||
logout(connection, false);
|
||||
} else if (error == "Connection closed"_ls) {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
// Failed due to network connection issue. This might happen when the homeserver is
|
||||
@@ -217,11 +251,11 @@ void Controller::invokeLogin()
|
||||
// connect to the homeserver. In this case, we don't want to do logout().
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
connection->logout(true);
|
||||
logout(connection, true);
|
||||
}
|
||||
Q_EMIT initiated();
|
||||
});
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
connect(connection, &Connection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||
});
|
||||
connection->assumeIdentity(account.userId(), accessToken);
|
||||
@@ -287,6 +321,22 @@ bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const
|
||||
return true;
|
||||
}
|
||||
|
||||
void Controller::changeAvatar(Connection *conn, const QUrl &localFile)
|
||||
{
|
||||
auto job = conn->uploadFile(localFile.toLocalFile());
|
||||
connect(job, &BaseJob::success, this, [conn, job] {
|
||||
conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::markAllMessagesAsRead(Connection *conn)
|
||||
{
|
||||
const auto rooms = conn->allRooms();
|
||||
for (auto room : rooms) {
|
||||
room->markAllMessagesAsRead();
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::supportSystemTray() const
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
@@ -297,6 +347,49 @@ bool Controller::supportSystemTray() const
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::changePassword(Connection *connection, const QString ¤tPassword, const QString &newPassword)
|
||||
{
|
||||
NeochatChangePasswordJob *job = connection->callApi<NeochatChangePasswordJob>(newPassword, false);
|
||||
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword, connection] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
QJsonObject authData;
|
||||
authData["session"_ls] = replyData["session"_ls];
|
||||
authData["password"_ls] = currentPassword;
|
||||
authData["type"_ls] = "m.login.password"_ls;
|
||||
authData["user"_ls] = connection->user()->id();
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, connection->user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
NeochatChangePasswordJob *innerJob = connection->callApi<NeochatChangePasswordJob>(newPassword, false, authData);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Success);
|
||||
});
|
||||
connect(innerJob, &BaseJob::failure, this, [innerJob, this]() {
|
||||
if (innerJob->jsonData()["errcode"_ls] == "M_FORBIDDEN"_ls) {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Wrong);
|
||||
} else {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Other);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
|
||||
{
|
||||
User *localUser = connection->user();
|
||||
QString decoded = avatarSource.path();
|
||||
if (decoded.isEmpty()) {
|
||||
connection->callApi<SetAvatarUrlJob>(localUser->id(), avatarSource);
|
||||
return true;
|
||||
}
|
||||
if (QImageReader(decoded).read().isNull()) {
|
||||
return false;
|
||||
} else {
|
||||
return localUser->setAvatar(decoded);
|
||||
}
|
||||
}
|
||||
|
||||
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password")
|
||||
{
|
||||
@@ -307,14 +400,6 @@ NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, b
|
||||
setRequestData(_data);
|
||||
}
|
||||
|
||||
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const Quotient::Omittable<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("DisableDeviceJob"), "_matrix/client/v3/account/deactivate")
|
||||
{
|
||||
QJsonObject data;
|
||||
addParam<IfNotEmpty>(data, QStringLiteral("auth"), auth);
|
||||
setRequestData(data);
|
||||
}
|
||||
|
||||
int Controller::accountCount() const
|
||||
{
|
||||
return m_accountRegistry.count();
|
||||
@@ -340,7 +425,7 @@ void Controller::setQuitOnLastWindowClosed()
|
||||
#endif
|
||||
}
|
||||
|
||||
NeoChatConnection *Controller::activeConnection() const
|
||||
Connection *Controller::activeConnection() const
|
||||
{
|
||||
if (m_connection.isNull()) {
|
||||
return nullptr;
|
||||
@@ -348,43 +433,49 @@ NeoChatConnection *Controller::activeConnection() const
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void Controller::setActiveConnection(NeoChatConnection *connection)
|
||||
void Controller::setActiveConnection(Connection *connection)
|
||||
{
|
||||
if (connection == m_connection) {
|
||||
return;
|
||||
}
|
||||
if (m_connection != nullptr) {
|
||||
disconnect(m_connection, &NeoChatConnection::syncError, this, nullptr);
|
||||
disconnect(m_connection, &NeoChatConnection::accountDataChanged, this, nullptr);
|
||||
disconnect(m_connection, &Connection::syncError, this, nullptr);
|
||||
disconnect(m_connection, &Connection::accountDataChanged, this, nullptr);
|
||||
}
|
||||
m_connection = connection;
|
||||
if (connection != nullptr) {
|
||||
NeoChatConfig::self()->setActiveConnection(connection->userId());
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this]() {
|
||||
connect(connection, &Connection::networkError, this, [this]() {
|
||||
if (!m_isOnline) {
|
||||
return;
|
||||
}
|
||||
m_isOnline = false;
|
||||
Q_EMIT isOnlineChanged(false);
|
||||
});
|
||||
connect(connection, &NeoChatConnection::syncDone, this, [this] {
|
||||
connect(connection, &Connection::syncDone, this, [this] {
|
||||
if (m_isOnline) {
|
||||
return;
|
||||
}
|
||||
m_isOnline = true;
|
||||
Q_EMIT isOnlineChanged(true);
|
||||
});
|
||||
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
|
||||
connect(connection, &Connection::requestFailed, this, [](BaseJob *job) {
|
||||
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
|
||||
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
|
||||
}
|
||||
});
|
||||
connect(connection, &Connection::accountDataChanged, this, [this](const QString &type) {
|
||||
if (type == QLatin1String("org.kde.neochat.account_label")) {
|
||||
Q_EMIT activeAccountLabelChanged();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
NeoChatConfig::self()->setActiveConnection(QString());
|
||||
}
|
||||
NeoChatConfig::self()->save();
|
||||
Q_EMIT activeConnectionChanged();
|
||||
Q_EMIT activeConnectionIndexChanged();
|
||||
Q_EMIT activeAccountLabelChanged();
|
||||
}
|
||||
|
||||
PushRuleModel *Controller::pushRuleModel() const
|
||||
@@ -555,6 +646,41 @@ bool Controller::isFlatpak() const
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::setActiveAccountLabel(const QString &label)
|
||||
{
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
QJsonObject json{
|
||||
{"account_label"_ls, label},
|
||||
};
|
||||
m_connection->setAccountData("org.kde.neochat.account_label"_ls, json);
|
||||
}
|
||||
|
||||
QString Controller::activeAccountLabel() const
|
||||
{
|
||||
if (!m_connection) {
|
||||
return {};
|
||||
}
|
||||
return m_connection->accountDataJson("org.kde.neochat.account_label"_ls)["account_label"_ls].toString();
|
||||
}
|
||||
|
||||
QVariantList Controller::getSupportedRoomVersions(Quotient::Connection *connection)
|
||||
{
|
||||
auto roomVersions = connection->availableRoomVersions();
|
||||
|
||||
QVariantList supportedRoomVersions;
|
||||
for (const Quotient::Connection::SupportedRoomVersion &v : roomVersions) {
|
||||
QVariantMap roomVersionMap;
|
||||
roomVersionMap.insert("id"_ls, v.id);
|
||||
roomVersionMap.insert("status"_ls, v.status);
|
||||
roomVersionMap.insert("isStable"_ls, v.isStable());
|
||||
supportedRoomVersions.append(roomVersionMap);
|
||||
}
|
||||
|
||||
return supportedRoomVersions;
|
||||
}
|
||||
|
||||
AccountRegistry &Controller::accounts()
|
||||
{
|
||||
return m_accountRegistry;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include <KFormat>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/settings.h>
|
||||
@@ -21,6 +20,7 @@ class QQuickTextDocument;
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
class Room;
|
||||
class User;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class Controller : public QObject
|
||||
/**
|
||||
* @brief The current connection for the rest of NeoChat to use.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatConnection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The PushRuleModel that has the active connection's push rules.
|
||||
@@ -62,6 +62,16 @@ class Controller : public QObject
|
||||
*/
|
||||
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
|
||||
|
||||
/**
|
||||
* @brief The account label for the active account.
|
||||
*
|
||||
* Account labels are a concept specific to NeoChat, allowing accounts to be
|
||||
* labelled, e.g. for "Work", "Private", etc.
|
||||
*
|
||||
* Set to an empty string to remove the label.
|
||||
*/
|
||||
Q_PROPERTY(QString activeAccountLabel READ activeAccountLabel WRITE setActiveAccountLabel NOTIFY activeAccountLabelChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the OS NeoChat is running on supports sytem tray icons.
|
||||
*/
|
||||
@@ -99,28 +109,46 @@ public:
|
||||
|
||||
[[nodiscard]] int accountCount() const;
|
||||
|
||||
void setActiveConnection(NeoChatConnection *connection);
|
||||
[[nodiscard]] NeoChatConnection *activeConnection() const;
|
||||
void setActiveConnection(Quotient::Connection *connection);
|
||||
[[nodiscard]] Quotient::Connection *activeConnection() const;
|
||||
|
||||
[[nodiscard]] PushRuleModel *pushRuleModel() const;
|
||||
|
||||
/**
|
||||
* @brief Add a new connection to the account registry.
|
||||
*/
|
||||
void addConnection(NeoChatConnection *c);
|
||||
void addConnection(Quotient::Connection *c);
|
||||
|
||||
/**
|
||||
* @brief Drop a connection from the account registry.
|
||||
*/
|
||||
void dropConnection(NeoChatConnection *c);
|
||||
void dropConnection(Quotient::Connection *c);
|
||||
|
||||
int activeConnectionIndex() const;
|
||||
|
||||
[[nodiscard]] QString activeAccountLabel() const;
|
||||
void setActiveAccountLabel(const QString &label);
|
||||
|
||||
/**
|
||||
* @brief Save an access token to the keychain for the given account.
|
||||
*/
|
||||
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
||||
|
||||
/**
|
||||
* @brief Change the password for an account.
|
||||
*
|
||||
* The function emits a passwordStatus signal with a PasswordStatus value when
|
||||
* complete.
|
||||
*
|
||||
* @sa PasswordStatus, passwordStatus
|
||||
*/
|
||||
Q_INVOKABLE void changePassword(Quotient::Connection *connection, const QString ¤tPassword, const QString &newPassword);
|
||||
|
||||
/**
|
||||
* @brief Change the avatar for an account.
|
||||
*/
|
||||
Q_INVOKABLE bool setAvatar(Quotient::Connection *connection, const QUrl &avatarSource);
|
||||
|
||||
/**
|
||||
* @brief Create new room for a group chat.
|
||||
*/
|
||||
@@ -182,12 +210,14 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
|
||||
|
||||
Q_INVOKABLE QVariantList getSupportedRoomVersions(Quotient::Connection *connection);
|
||||
|
||||
Quotient::AccountRegistry &accounts();
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
QPointer<Quotient::Connection> m_connection;
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
|
||||
@@ -214,8 +244,8 @@ Q_SIGNALS:
|
||||
/// Error occurred because of server or bug in NeoChat
|
||||
void globalErrorOccured(QString error, QString detail);
|
||||
void syncDone();
|
||||
void connectionAdded(NeoChatConnection *connection);
|
||||
void connectionDropped(NeoChatConnection *connection);
|
||||
void connectionAdded(Quotient::Connection *_t1);
|
||||
void connectionDropped(Quotient::Connection *_t1);
|
||||
void accountCountChanged();
|
||||
void initiated();
|
||||
void notificationClicked(const QString &_t1, const QString &_t2);
|
||||
@@ -226,14 +256,18 @@ Q_SIGNALS:
|
||||
void userConsentRequired(QUrl url);
|
||||
void testConnectionResult(const QString &connection, bool usable);
|
||||
void isOnlineChanged(bool isOnline);
|
||||
void keyVerificationRequest(int timeLeft, NeoChatConnection *connection, const QString &transactionId, const QString &deviceId);
|
||||
void keyVerificationRequest(int timeLeft, Quotient::Connection *connection, const QString &transactionId, const QString &deviceId);
|
||||
void keyVerificationStart();
|
||||
void keyVerificationAccept(const QString &commitment);
|
||||
void keyVerificationKey(const QString &sas);
|
||||
void activeConnectionIndexChanged();
|
||||
void roomAdded(NeoChatRoom *room);
|
||||
void activeAccountLabelChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void logout(Quotient::Connection *conn, bool serverSideLogout);
|
||||
void changeAvatar(Quotient::Connection *conn, const QUrl &localFile);
|
||||
static void markAllMessagesAsRead(Quotient::Connection *conn);
|
||||
void saveWindowGeometry();
|
||||
};
|
||||
|
||||
@@ -249,9 +283,3 @@ class NeochatDeleteDeviceJob : public Quotient::BaseJob
|
||||
public:
|
||||
explicit NeochatDeleteDeviceJob(const QString &deviceId, const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
|
||||
};
|
||||
|
||||
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeoChatDeactivateAccountJob(const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
|
||||
};
|
||||
|
||||
@@ -124,7 +124,7 @@ int DelegateSizeHelper::calculateCurrentPercentageWidth() const
|
||||
int maxPercentWidth = endPercentBigger ? m_endPercentWidth : m_startPercentWidth;
|
||||
int minPercentWidth = endPercentBigger ? m_startPercentWidth : m_endPercentWidth;
|
||||
|
||||
int calcPercentWidth = std::round(m * m_parentWidth + c);
|
||||
int calcPercentWidth = std::ceil(m * m_parentWidth + c);
|
||||
return std::clamp(calcPercentWidth, minPercentWidth, maxPercentWidth);
|
||||
}
|
||||
|
||||
@@ -146,9 +146,9 @@ qreal DelegateSizeHelper::currentWidth() const
|
||||
|
||||
qreal absoluteWidth = m_parentWidth * percentWidth * 0.01;
|
||||
if (m_maxWidth < 0.0) {
|
||||
return std::round(absoluteWidth);
|
||||
return std::ceil(absoluteWidth);
|
||||
} else {
|
||||
return std::round(std::min(absoluteWidth, m_maxWidth));
|
||||
return std::ceil(std::min(absoluteWidth, m_maxWidth));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -20,10 +19,6 @@ LinkPreviewer::LinkPreviewer(QObject *parent, NeoChatRoom *room, const QUrl &url
|
||||
, m_url(url)
|
||||
{
|
||||
loadUrlPreview();
|
||||
if (m_currentRoom) {
|
||||
connect(m_currentRoom, &NeoChatRoom::urlPreviewEnabledChanged, this, &LinkPreviewer::loadUrlPreview);
|
||||
}
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
|
||||
}
|
||||
|
||||
bool LinkPreviewer::loaded() const
|
||||
@@ -62,9 +57,6 @@ void LinkPreviewer::setUrl(QUrl url)
|
||||
|
||||
void LinkPreviewer::loadUrlPreview()
|
||||
{
|
||||
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (m_url.scheme() == QStringLiteral("https")) {
|
||||
m_loaded = false;
|
||||
Q_EMIT loadedChanged();
|
||||
|
||||
@@ -22,7 +22,7 @@ Login::Login(QObject *parent)
|
||||
void Login::init()
|
||||
{
|
||||
m_homeserverReachable = false;
|
||||
m_connection = new NeoChatConnection();
|
||||
m_connection = new Connection();
|
||||
m_matrixId = QString();
|
||||
m_password = QString();
|
||||
m_deviceName = QStringLiteral("NeoChat %1 %2 %3 %4")
|
||||
@@ -51,7 +51,7 @@ void Login::init()
|
||||
m_testing = true;
|
||||
Q_EMIT testingChanged();
|
||||
if (!m_connection) {
|
||||
m_connection = new NeoChatConnection();
|
||||
m_connection = new Connection();
|
||||
}
|
||||
m_connection->resolveServer(m_matrixId);
|
||||
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() {
|
||||
@@ -87,11 +87,7 @@ void Login::init()
|
||||
Q_EMIT isLoggingInChanged();
|
||||
});
|
||||
connect(m_connection, &Connection::loginError, this, [this](QString error, const QString &) {
|
||||
if (error == QStringLiteral("Invalid username or password")) {
|
||||
setInvalidPassword(true);
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
}
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
m_isLoggingIn = false;
|
||||
Q_EMIT isLoggingInChanged();
|
||||
});
|
||||
@@ -137,7 +133,6 @@ QString Login::password() const
|
||||
|
||||
void Login::setPassword(const QString &password)
|
||||
{
|
||||
setInvalidPassword(false);
|
||||
m_password = password;
|
||||
Q_EMIT passwordChanged();
|
||||
}
|
||||
@@ -204,15 +199,4 @@ bool Login::isLoggedIn() const
|
||||
return m_isLoggedIn;
|
||||
}
|
||||
|
||||
void Login::setInvalidPassword(bool invalid)
|
||||
{
|
||||
m_invalidPassword = invalid;
|
||||
Q_EMIT isInvalidPasswordChanged();
|
||||
}
|
||||
|
||||
bool Login::isInvalidPassword() const
|
||||
{
|
||||
return m_invalidPassword;
|
||||
}
|
||||
|
||||
#include "moc_login.cpp"
|
||||
|
||||
17
src/login.h
17
src/login.h
@@ -6,7 +6,10 @@
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
class NeoChatConnection;
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Login
|
||||
@@ -70,11 +73,6 @@ class Login : public QObject
|
||||
*/
|
||||
Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the password (or the username) is invalid.
|
||||
*/
|
||||
Q_PROPERTY(bool isInvalidPassword READ isInvalidPassword NOTIFY isInvalidPasswordChanged)
|
||||
|
||||
public:
|
||||
explicit Login(QObject *parent = nullptr);
|
||||
|
||||
@@ -102,9 +100,6 @@ public:
|
||||
|
||||
bool isLoggedIn() const;
|
||||
|
||||
bool isInvalidPassword() const;
|
||||
void setInvalidPassword(bool invalid);
|
||||
|
||||
Q_INVOKABLE void login();
|
||||
Q_INVOKABLE void loginWithSso();
|
||||
|
||||
@@ -121,7 +116,6 @@ Q_SIGNALS:
|
||||
void testingChanged();
|
||||
void isLoggingInChanged();
|
||||
void isLoggedInChanged();
|
||||
void isInvalidPasswordChanged();
|
||||
|
||||
private:
|
||||
void setHomeserverReachable(bool reachable);
|
||||
@@ -132,10 +126,9 @@ private:
|
||||
QString m_deviceName;
|
||||
bool m_supportsSso = false;
|
||||
bool m_supportsPassword = false;
|
||||
NeoChatConnection *m_connection = nullptr;
|
||||
Quotient::Connection *m_connection = nullptr;
|
||||
QUrl m_ssoUrl;
|
||||
bool m_testing = false;
|
||||
bool m_isLoggingIn = false;
|
||||
bool m_isLoggedIn = false;
|
||||
bool m_invalidPassword = false;
|
||||
};
|
||||
|
||||
15
src/main.cpp
15
src/main.cpp
@@ -18,10 +18,6 @@
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_WEBVIEW
|
||||
#include <QtWebView>
|
||||
#endif
|
||||
|
||||
#include <KAboutData>
|
||||
#ifdef HAVE_KDBUSADDONS
|
||||
#include <KDBusService>
|
||||
@@ -54,6 +50,7 @@
|
||||
#include "login.h"
|
||||
#include "matriximageprovider.h"
|
||||
#include "models/accountemoticonmodel.h"
|
||||
#include "models/collapsestateproxymodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/devicesmodel.h"
|
||||
#include "models/devicesproxymodel.h"
|
||||
@@ -80,7 +77,6 @@
|
||||
#include "models/userlistmodel.h"
|
||||
#include "models/webshortcutmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "pollhandler.h"
|
||||
@@ -99,7 +95,6 @@
|
||||
#include "runner.h"
|
||||
#include <QDBusConnection>
|
||||
#endif
|
||||
#include "registration.h"
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include <Windows.h>
|
||||
@@ -144,10 +139,6 @@ int main(int argc, char *argv[])
|
||||
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
|
||||
#ifdef HAVE_WEBVIEW
|
||||
QtWebView::initialize();
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
QGuiApplication app(argc, argv);
|
||||
QQuickStyle::setStyle(QStringLiteral("org.kde.breeze"));
|
||||
@@ -242,7 +233,6 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Controller::instance().accounts());
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "SpaceHierarchyCache", &SpaceHierarchyCache::instance());
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "CustomEmojiModel", &CustomEmojiModel::instance());
|
||||
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Registration", &Registration::instance());
|
||||
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
|
||||
qmlRegisterType<ChatDocumentHandler>("org.kde.neochat", 1, 0, "ChatDocumentHandler");
|
||||
qmlRegisterType<RoomListModel>("org.kde.neochat", 1, 0, "RoomListModel");
|
||||
@@ -250,6 +240,7 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
|
||||
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
||||
qmlRegisterType<ReactionModel>("org.kde.neochat", 1, 0, "ReactionModel");
|
||||
qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel");
|
||||
qmlRegisterType<MediaMessageFilterModel>("org.kde.neochat", 1, 0, "MediaMessageFilterModel");
|
||||
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
||||
qmlRegisterType<UserFilterModel>("org.kde.neochat", 1, 0, "UserFilterModel");
|
||||
@@ -282,12 +273,12 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM"_ls);
|
||||
qmlRegisterUncreatableType<User>("org.kde.neochat", 1, 0, "User", {});
|
||||
qmlRegisterUncreatableType<NeoChatRoom>("org.kde.neochat", 1, 0, "NeoChatRoom", {});
|
||||
qmlRegisterUncreatableType<NeoChatConnection>("org.kde.neochat", 1, 0, "NeoChatConnection", {});
|
||||
|
||||
qRegisterMetaType<User *>("User*");
|
||||
qRegisterMetaType<User *>("const User*");
|
||||
qRegisterMetaType<User *>("const Quotient::User*");
|
||||
qRegisterMetaType<Room *>("Room*");
|
||||
qRegisterMetaType<Connection *>("Connection*");
|
||||
qRegisterMetaType<MessageEventType>("MessageEventType");
|
||||
qRegisterMetaType<NeoChatRoom *>("NeoChatRoom*");
|
||||
qRegisterMetaType<User *>("User*");
|
||||
|
||||
177
src/models/collapsestateproxymodel.cpp
Normal file
177
src/models/collapsestateproxymodel.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "collapsestateproxymodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||
|| (source_row < sourceModel()->rowCount() - 1
|
||||
&& sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State) // If this is the first state in a block, show it. TODO hidden events?
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it
|
||||
}
|
||||
|
||||
QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == AggregateDisplayRole) {
|
||||
return aggregateEventToString(mapToSource(index).row());
|
||||
} else if (role == StateEventsRole) {
|
||||
return stateEventsList(mapToSource(index).row());
|
||||
} else if (role == AuthorListRole) {
|
||||
return authorList(mapToSource(index).row());
|
||||
} else if (role == ExcessAuthorsRole) {
|
||||
return excessAuthors(mapToSource(index).row());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
||||
{
|
||||
auto roles = sourceModel()->roleNames();
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
roles[StateEventsRole] = "stateEvents";
|
||||
roles[AuthorListRole] = "authorList";
|
||||
roles[ExcessAuthorsRole] = "excessAuthors";
|
||||
return roles;
|
||||
}
|
||||
|
||||
QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
{
|
||||
QStringList parts;
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||
QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||
uniqueAuthors.append(nextAuthor);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
parts.sort(); // Sort them so that all identical events can be collected.
|
||||
if (!parts.isEmpty()) {
|
||||
QStringList chunks;
|
||||
while (!parts.isEmpty()) {
|
||||
chunks += QString();
|
||||
int count = 1;
|
||||
auto part = parts.takeFirst();
|
||||
chunks.last() += part;
|
||||
while (!parts.isEmpty() && parts.first() == part) {
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
if (count > 1 && uniqueAuthors.length() == 1) {
|
||||
chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count);
|
||||
}
|
||||
}
|
||||
chunks.removeDuplicates();
|
||||
QString text = QStringLiteral("<style>a {text-decoration: none;}</style>"); // There can be links in the event text so make sure all are styled.
|
||||
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("color")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped());
|
||||
text += userText;
|
||||
text += chunks.takeFirst();
|
||||
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
text += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
return text;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
||||
{
|
||||
QVariantList stateEvents;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
auto nextState = QVariantMap{
|
||||
{QStringLiteral("author"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
|
||||
{QStringLiteral("authorDisplayName"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
|
||||
{QStringLiteral("text"), sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
};
|
||||
stateEvents.append(nextState);
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
|
||||
QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
uniqueAuthors = uniqueAuthors.mid(0, 5);
|
||||
}
|
||||
return uniqueAuthors;
|
||||
}
|
||||
|
||||
QString CollapseStateProxyModel::excessAuthors(int row) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = row; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int excessAuthors;
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
excessAuthors = uniqueAuthors.count() - 5;
|
||||
} else {
|
||||
excessAuthors = 0;
|
||||
}
|
||||
QString excessAuthorsString;
|
||||
if (excessAuthors == 0) {
|
||||
return QString();
|
||||
} else {
|
||||
return QStringLiteral("+ %1").arg(excessAuthors);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_collapsestateproxymodel.cpp"
|
||||
80
src/models/collapsestateproxymodel.h
Normal file
80
src/models/collapsestateproxymodel.h
Normal file
@@ -0,0 +1,80 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class CollapseStateProxyModel
|
||||
*
|
||||
* This model aggregates multiple sequential state events into a single entry.
|
||||
*
|
||||
* Events are only aggregated if they happened on the same day.
|
||||
*
|
||||
* @sa MessageEventModel
|
||||
*/
|
||||
class CollapseStateProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */
|
||||
StateEventsRole, /**< List of state events in the aggregated state. */
|
||||
AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */
|
||||
ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Whether a row should be shown out or not.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||
*/
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractProxyModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Aggregation of the text of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QString aggregateEventToString(int row) const;
|
||||
|
||||
/**
|
||||
* @brief Return a list of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief List of the first 5 unique authors for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QVariantList authorList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief The number of unique authors beyond the first 5 for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QString excessAuthors(int row) const;
|
||||
};
|
||||
@@ -145,7 +145,7 @@ void CompletionModel::updateCompletion()
|
||||
m_filterModel->setFullText(m_fullText);
|
||||
m_filterModel->setFilterText(m_text);
|
||||
m_filterModel->invalidate();
|
||||
} else if (text().startsWith(QLatin1Char(':')) && text().size() > 1 && !text()[1].isUpper()
|
||||
} else if (text().startsWith(QLatin1Char(':')) && !text()[1].isUpper()
|
||||
&& (m_fullText.indexOf(QLatin1Char(':'), 1) == -1
|
||||
|| (m_fullText.indexOf(QLatin1Char(' ')) != -1 && m_fullText.indexOf(QLatin1Char(':'), 1) > m_fullText.indexOf(QLatin1Char(' '), 1)))) {
|
||||
m_filterModel->setSourceModel(m_emojiModel);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "models/messagefiltermodel.h"
|
||||
#include "models/collapsestateproxymodel.h"
|
||||
|
||||
/**
|
||||
* @class MediaMessageFilterModel
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
SourceRole = MessageFilterModel::LastRole + 1, /**< The mxc source URL for the item. */
|
||||
SourceRole = CollapseStateProxyModel::LastRole + 1, /**< The mxc source URL for the item. */
|
||||
TempSourceRole, /**< Source for the temporary content (either blurhash or mxc URL). */
|
||||
TypeRole, /**< The type of the media (image or video). */
|
||||
CaptionRole, /**< The caption for the item. */
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#include "messagefiltermodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
@@ -34,192 +32,22 @@ bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour
|
||||
{
|
||||
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
|
||||
// Don't show redacted (i.e. deleted) messages.
|
||||
if (index.data(MessageEventModel::IsRedactedRole).toBool() && !NeoChatConfig::self()->showDeletedMessages()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show hidden or replaced messages.
|
||||
const int specialMarks = index.data(MessageEventModel::SpecialMarksRole).toInt();
|
||||
if (specialMarks == EventStatus::Hidden || specialMarks == EventStatus::Replaced) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show events with an unknown type.
|
||||
const auto eventType = index.data(MessageEventModel::DelegateTypeRole).toInt();
|
||||
if (eventType == MessageEventModel::Other) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show state events that are not the first in a consecutive group on the
|
||||
// same day as they will be grouped as a single delegate.
|
||||
const bool notLastRow = sourceRow < sourceModel()->rowCount() - 1;
|
||||
const bool previousEventIsState =
|
||||
sourceModel()->data(sourceModel()->index(sourceRow + 1, 0), MessageEventModel::DelegateTypeRole) == MessageEventModel::DelegateType::State;
|
||||
const bool newDay = sourceModel()->data(sourceModel()->index(sourceRow, 0), MessageEventModel::ShowSectionRole).toBool();
|
||||
if (eventType == MessageEventModel::State && notLastRow && previousEventIsState && !newDay) {
|
||||
if (eventType == MessageEventModel::Other) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant MessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == AggregateDisplayRole) {
|
||||
return aggregateEventToString(mapToSource(index).row());
|
||||
} else if (role == StateEventsRole) {
|
||||
return stateEventsList(mapToSource(index).row());
|
||||
} else if (role == AuthorListRole) {
|
||||
return authorList(mapToSource(index).row());
|
||||
} else if (role == ExcessAuthorsRole) {
|
||||
return excessAuthors(mapToSource(index).row());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> MessageFilterModel::roleNames() const
|
||||
{
|
||||
auto roles = sourceModel()->roleNames();
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
roles[StateEventsRole] = "stateEvents";
|
||||
roles[AuthorListRole] = "authorList";
|
||||
roles[ExcessAuthorsRole] = "excessAuthors";
|
||||
return roles;
|
||||
}
|
||||
|
||||
QString MessageFilterModel::aggregateEventToString(int sourceRow) const
|
||||
{
|
||||
QStringList parts;
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString();
|
||||
QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAuthor)) {
|
||||
uniqueAuthors.append(nextAuthor);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
parts.sort(); // Sort them so that all identical events can be collected.
|
||||
if (!parts.isEmpty()) {
|
||||
QStringList chunks;
|
||||
while (!parts.isEmpty()) {
|
||||
chunks += QString();
|
||||
int count = 1;
|
||||
auto part = parts.takeFirst();
|
||||
chunks.last() += part;
|
||||
while (!parts.isEmpty() && parts.first() == part) {
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
if (count > 1 && uniqueAuthors.length() == 1) {
|
||||
chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count);
|
||||
}
|
||||
}
|
||||
chunks.removeDuplicates();
|
||||
QString text = QStringLiteral("<style>a {text-decoration: none;}</style>"); // There can be links in the event text so make sure all are styled.
|
||||
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("color")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped());
|
||||
text += userText;
|
||||
text += chunks.takeFirst();
|
||||
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
text += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
return text;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList MessageFilterModel::stateEventsList(int sourceRow) const
|
||||
{
|
||||
QVariantList stateEvents;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
auto nextState = QVariantMap{
|
||||
{QStringLiteral("author"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
|
||||
{QStringLiteral("authorDisplayName"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
|
||||
{QStringLiteral("text"), sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
};
|
||||
stateEvents.append(nextState);
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stateEvents;
|
||||
}
|
||||
|
||||
QVariantList MessageFilterModel::authorList(int sourceRow) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
uniqueAuthors = uniqueAuthors.mid(0, 5);
|
||||
}
|
||||
return uniqueAuthors;
|
||||
}
|
||||
|
||||
QString MessageFilterModel::excessAuthors(int row) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = row; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (i > 0
|
||||
&& (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int excessAuthors;
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
excessAuthors = uniqueAuthors.count() - 5;
|
||||
} else {
|
||||
excessAuthors = 0;
|
||||
}
|
||||
QString excessAuthorsString;
|
||||
if (excessAuthors == 0) {
|
||||
return QString();
|
||||
} else {
|
||||
return QStringLiteral("+ %1").arg(excessAuthors);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_messagefiltermodel.cpp"
|
||||
|
||||
@@ -5,79 +5,21 @@
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
|
||||
/**
|
||||
* @class MessageFilterModel
|
||||
*
|
||||
* This model filters out any messages that should be hidden.
|
||||
*
|
||||
* Deleted messages are only hidden if the user hasn't set them to be shown.
|
||||
*
|
||||
* The model also contains the roles and functions to support aggregating multiple
|
||||
* consecutive state events into a single delegate. The state events must all happen
|
||||
* on the same day to be aggregated.
|
||||
*/
|
||||
class MessageFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */
|
||||
StateEventsRole, /**< List of state events in the aggregated state. */
|
||||
AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */
|
||||
ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
|
||||
explicit MessageFilterModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Custom filter function to remove hidden messages.
|
||||
*/
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractProxyModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Aggregation of the text of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QString aggregateEventToString(int row) const;
|
||||
|
||||
/**
|
||||
* @brief Return a list of consecutive state events starting at row.
|
||||
*
|
||||
* If state events happen on different days they will be split into two aggregate
|
||||
* events.
|
||||
*/
|
||||
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief List of the first 5 unique authors for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QVariantList authorList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief The number of unique authors beyond the first 5 for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QString excessAuthors(int row) const;
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
if (role == TextRole) {
|
||||
if (reaction.authors.count() > 1) {
|
||||
return QStringLiteral("%1 %2").arg(reaction.reaction, QString::number(reaction.authors.count()));
|
||||
return QStringLiteral("%1 %2").arg(reaction.reaction, reaction.authors.count());
|
||||
} else {
|
||||
return reaction.reaction;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@ QVariant StateModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
int StateModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return m_room->currentState().events().size();
|
||||
return !m_room || parent.isValid() ? 0 : m_room->currentState().events().size();
|
||||
}
|
||||
|
||||
NeoChatRoom *StateModel::room() const
|
||||
@@ -37,8 +36,12 @@ NeoChatRoom *StateModel::room() const
|
||||
|
||||
void StateModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (m_room == room) {
|
||||
return;
|
||||
}
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
|
||||
beginResetModel();
|
||||
m_stateEvents.clear();
|
||||
m_stateEvents = m_room->currentState().events().keys();
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "neochatconnection.h"
|
||||
|
||||
#include <QImageReader>
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <qt5keychain/keychain.h>
|
||||
#else
|
||||
#include <qt6keychain/keychain.h>
|
||||
#endif
|
||||
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
#include <Quotient/csapi/profile.h>
|
||||
#include <Quotient/settings.h>
|
||||
#include <Quotient/user.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeoChatConnection::NeoChatConnection(QObject *parent)
|
||||
: Connection(parent)
|
||||
{
|
||||
connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
|
||||
if (type == QLatin1String("org.kde.neochat.account_label")) {
|
||||
Q_EMIT labelChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
|
||||
: Connection(server, parent)
|
||||
{
|
||||
connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
|
||||
if (type == QLatin1String("org.kde.neochat.account_label")) {
|
||||
Q_EMIT labelChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NeoChatConnection::logout(bool serverSideLogout)
|
||||
{
|
||||
SettingsGroup(QStringLiteral("Accounts")).remove(userId());
|
||||
|
||||
QKeychain::DeletePasswordJob job(qAppName());
|
||||
job.setAutoDelete(true);
|
||||
job.setKey(userId());
|
||||
QEventLoop loop;
|
||||
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||
job.start();
|
||||
loop.exec();
|
||||
|
||||
if (Controller::instance().accounts().count() > 1) {
|
||||
// Only set the connection if the the account being logged out is currently active
|
||||
if (this == Controller::instance().activeConnection()) {
|
||||
Controller::instance().setActiveConnection(dynamic_cast<NeoChatConnection *>(Controller::instance().accounts().accounts()[0]));
|
||||
}
|
||||
} else {
|
||||
Controller::instance().setActiveConnection(nullptr);
|
||||
}
|
||||
if (!serverSideLogout) {
|
||||
return;
|
||||
}
|
||||
Connection::logout();
|
||||
}
|
||||
|
||||
bool NeoChatConnection::setAvatar(const QUrl &avatarSource)
|
||||
{
|
||||
QString decoded = avatarSource.path();
|
||||
if (decoded.isEmpty()) {
|
||||
callApi<SetAvatarUrlJob>(user()->id(), avatarSource);
|
||||
return true;
|
||||
}
|
||||
if (QImageReader(decoded).read().isNull()) {
|
||||
return false;
|
||||
} else {
|
||||
return user()->setAvatar(decoded);
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList NeoChatConnection::getSupportedRoomVersions() const
|
||||
{
|
||||
const auto &roomVersions = availableRoomVersions();
|
||||
QVariantList supportedRoomVersions;
|
||||
for (const auto &v : roomVersions) {
|
||||
QVariantMap roomVersionMap;
|
||||
roomVersionMap.insert("id"_ls, v.id);
|
||||
roomVersionMap.insert("status"_ls, v.status);
|
||||
roomVersionMap.insert("isStable"_ls, v.isStable());
|
||||
supportedRoomVersions.append(roomVersionMap);
|
||||
}
|
||||
return supportedRoomVersions;
|
||||
}
|
||||
|
||||
void NeoChatConnection::changePassword(const QString ¤tPassword, const QString &newPassword)
|
||||
{
|
||||
auto job = callApi<NeochatChangePasswordJob>(newPassword, false);
|
||||
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
QJsonObject authData;
|
||||
authData["session"_ls] = replyData["session"_ls];
|
||||
authData["password"_ls] = currentPassword;
|
||||
authData["type"_ls] = "m.login.password"_ls;
|
||||
authData["user"_ls] = user()->id();
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
NeochatChangePasswordJob *innerJob = callApi<NeochatChangePasswordJob>(newPassword, false, authData);
|
||||
connect(innerJob, &BaseJob::success, this, []() {
|
||||
Q_EMIT Controller::instance().passwordStatus(Controller::PasswordStatus::Success);
|
||||
});
|
||||
connect(innerJob, &BaseJob::failure, this, [innerJob]() {
|
||||
Q_EMIT Controller::instance().passwordStatus(innerJob->jsonData()["errcode"_ls] == "M_FORBIDDEN"_ls ? Controller::PasswordStatus::Wrong
|
||||
: Controller::PasswordStatus::Other);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NeoChatConnection::setLabel(const QString &label)
|
||||
{
|
||||
QJsonObject json{
|
||||
{"account_label"_ls, label},
|
||||
};
|
||||
setAccountData("org.kde.neochat.account_label"_ls, json);
|
||||
Q_EMIT labelChanged();
|
||||
}
|
||||
|
||||
QString NeoChatConnection::label() const
|
||||
{
|
||||
return accountDataJson("org.kde.neochat.account_label"_ls)["account_label"_ls].toString();
|
||||
}
|
||||
|
||||
void NeoChatConnection::deactivateAccount(const QString &password)
|
||||
{
|
||||
auto job = callApi<NeoChatDeactivateAccountJob>();
|
||||
connect(job, &BaseJob::result, this, [this, job, password] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
QJsonObject authData;
|
||||
authData["session"_ls] = replyData["session"_ls];
|
||||
authData["password"_ls] = password;
|
||||
authData["type"_ls] = "m.login.password"_ls;
|
||||
authData["user"_ls] = user()->id();
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
auto innerJob = callApi<NeoChatDeactivateAccountJob>(authData);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
logout(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
class NeoChatConnection : public Quotient::Connection
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The account label for this account.
|
||||
*
|
||||
* Account labels are a concept specific to NeoChat, allowing accounts to be
|
||||
* labelled, e.g. for "Work", "Private", etc.
|
||||
*
|
||||
* Set to an empty string to remove the label.
|
||||
*/
|
||||
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
|
||||
|
||||
public:
|
||||
NeoChatConnection(QObject *parent = nullptr);
|
||||
NeoChatConnection(const QUrl &server, QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void logout(bool serverSideLogout);
|
||||
Q_INVOKABLE QVariantList getSupportedRoomVersions() const;
|
||||
|
||||
/**
|
||||
* @brief Change the password for an account.
|
||||
*
|
||||
* The function emits a passwordStatus signal with a PasswordStatus value when
|
||||
* complete.
|
||||
*
|
||||
* @sa PasswordStatus, passwordStatus
|
||||
*/
|
||||
Q_INVOKABLE void changePassword(const QString ¤tPassword, const QString &newPassword);
|
||||
|
||||
/**
|
||||
* @brief Change the avatar for an account.
|
||||
*/
|
||||
Q_INVOKABLE bool setAvatar(const QUrl &avatarSource);
|
||||
|
||||
[[nodiscard]] QString label() const;
|
||||
void setLabel(const QString &label);
|
||||
|
||||
Q_INVOKABLE void deactivateAccount(const QString &password);
|
||||
|
||||
Q_SIGNALS:
|
||||
void labelChanged();
|
||||
};
|
||||
@@ -111,7 +111,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
qWarning() << "using this room's avatar";
|
||||
avatar_image = avatar(128);
|
||||
}
|
||||
NotificationsManager::instance().postInviteNotification(this, displayNameForHtml(), htmlSafeMemberName(senderId), avatar_image);
|
||||
NotificationsManager::instance().postInviteNotification(this, htmlSafeDisplayName(), htmlSafeMemberName(senderId), avatar_image);
|
||||
});
|
||||
connect(this, &Room::changed, this, [this] {
|
||||
Q_EMIT canEncryptRoomChanged();
|
||||
@@ -978,6 +978,11 @@ bool NeoChatRoom::isUserBanned(const QString &user) const
|
||||
return roomMemberEvent->membership() == Membership::Ban;
|
||||
}
|
||||
|
||||
QString NeoChatRoom::htmlSafeDisplayName() const
|
||||
{
|
||||
return displayName().toHtmlEscaped();
|
||||
}
|
||||
|
||||
void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reason)
|
||||
{
|
||||
doDeleteMessagesByUser(user, reason);
|
||||
|
||||
@@ -111,6 +111,11 @@ class NeoChatRoom : public Quotient::Room
|
||||
*/
|
||||
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
||||
|
||||
/**
|
||||
* @brief Display name with any html special characters escaped.
|
||||
*/
|
||||
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
||||
|
||||
/**
|
||||
* @brief The avatar image to be used for the room.
|
||||
*/
|
||||
@@ -586,6 +591,8 @@ public:
|
||||
|
||||
[[nodiscard]] bool readMarkerLoaded() const;
|
||||
|
||||
QString htmlSafeDisplayName() const;
|
||||
|
||||
/**
|
||||
* @brief Get subtitle text for room
|
||||
*
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
|
||||
#include <QPainter>
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/csapi/pushrules.h>
|
||||
#include <Quotient/user.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
#include "texthandler.h"
|
||||
@@ -37,7 +37,7 @@ NotificationsManager::NotificationsManager(QObject *parent)
|
||||
{
|
||||
}
|
||||
|
||||
void NotificationsManager::handleNotifications(QPointer<NeoChatConnection> connection)
|
||||
void NotificationsManager::handleNotifications(QPointer<Connection> connection)
|
||||
{
|
||||
if (!m_connActiveJob.contains(connection->user()->id())) {
|
||||
auto job = connection->callApi<GetNotificationsJob>();
|
||||
@@ -49,7 +49,7 @@ void NotificationsManager::handleNotifications(QPointer<NeoChatConnection> conne
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization)
|
||||
void NotificationsManager::processNotificationJob(QPointer<Quotient::Connection> connection, Quotient::GetNotificationsJob *job, bool initialization)
|
||||
{
|
||||
if (job == nullptr) {
|
||||
return;
|
||||
@@ -145,7 +145,7 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
|
||||
}
|
||||
}
|
||||
|
||||
bool NotificationsManager::shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue ¬ification)
|
||||
bool NotificationsManager::shouldPostNotification(QPointer<Quotient::Connection> connection, const QJsonValue ¬ification)
|
||||
{
|
||||
if (connection == nullptr) {
|
||||
return false;
|
||||
@@ -211,7 +211,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
return;
|
||||
}
|
||||
if (room->localUser()->id() != Controller::instance().activeConnection()->userId()) {
|
||||
Controller::instance().setActiveConnection(dynamic_cast<NeoChatConnection *>(Controller::instance().accounts().get(room->localUser()->id())));
|
||||
Controller::instance().setActiveConnection(Controller::instance().accounts().get(room->localUser()->id()));
|
||||
}
|
||||
RoomManager::instance().enterRoom(room);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
|
||||
class NeoChatConnection;
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
}
|
||||
|
||||
class KNotification;
|
||||
class NeoChatRoom;
|
||||
|
||||
@@ -76,7 +80,7 @@ public:
|
||||
/**
|
||||
* @brief Handle the notifications for the given connection.
|
||||
*/
|
||||
void handleNotifications(QPointer<NeoChatConnection> connection);
|
||||
void handleNotifications(QPointer<Quotient::Connection> connection);
|
||||
|
||||
private:
|
||||
explicit NotificationsManager(QObject *parent = nullptr);
|
||||
@@ -86,13 +90,13 @@ private:
|
||||
|
||||
QStringList m_connActiveJob;
|
||||
|
||||
bool shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue ¬ification);
|
||||
bool shouldPostNotification(QPointer<Quotient::Connection> connection, const QJsonValue ¬ification);
|
||||
|
||||
QHash<QString, KNotification *> m_notifications;
|
||||
QHash<QString, QPointer<KNotification>> m_invitations;
|
||||
|
||||
private Q_SLOTS:
|
||||
void processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization);
|
||||
void processNotificationJob(QPointer<Quotient::Connection> connection, Quotient::GetNotificationsJob *job, bool initialization);
|
||||
|
||||
private:
|
||||
QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room);
|
||||
|
||||
@@ -6,107 +6,122 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
ColumnLayout {
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormComboBoxDelegate {
|
||||
text: i18n("Room")
|
||||
textRole: "displayName"
|
||||
valueRole: "id"
|
||||
model: RoomListModel {
|
||||
id: roomListModel
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
Component.onCompleted: currentIndex = indexOfValue(room.id)
|
||||
onCurrentValueChanged: room = roomListModel.roomByAliasOrId(currentValue)
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
|
||||
required property var room
|
||||
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.gridUnit
|
||||
|
||||
FormCard.FormComboBoxDelegate {
|
||||
id: roomChooser
|
||||
text: i18n("Room")
|
||||
textRole: "displayName"
|
||||
valueRole: "roomId"
|
||||
model: RoomListModel {
|
||||
id: roomListModel
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
MobileForm.FormCheckDelegate {
|
||||
text: i18n("Show m.room.member events")
|
||||
checked: true
|
||||
onToggled: {
|
||||
if (checked) {
|
||||
stateEventFilterModel.removeStateEventTypeFiltered("m.room.member");
|
||||
} else {
|
||||
stateEventFilterModel.addStateEventTypeFiltered("m.room.member");
|
||||
}
|
||||
onCurrentValueChanged: room = roomListModel.roomByAliasOrId(currentValue)
|
||||
Component.onCompleted: currentIndex = indexOfValue(room.id)
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: showRoomMember }
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
id: showRoomMember
|
||||
text: i18n("Show m.room.member events")
|
||||
checked: true
|
||||
onToggled: {
|
||||
if (checked) {
|
||||
stateEventFilterModel.removeStateEventTypeFiltered("m.room.member");
|
||||
} else {
|
||||
stateEventFilterModel.addStateEventTypeFiltered("m.room.member");
|
||||
}
|
||||
}
|
||||
MobileForm.FormCheckDelegate {
|
||||
id: roomAccoutnDataVisibleCheck
|
||||
text: i18n("Show room account data")
|
||||
checked: false
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: roomAccoutnDataVisibleCheck; below: showRoomMember }
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
id: roomAccoutnDataVisibleCheck
|
||||
text: i18n("Show room account data")
|
||||
checked: false
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { below: roomAccoutnDataVisibleCheck }
|
||||
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("Room id")
|
||||
description: room.id
|
||||
}
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18n("Room Account Data for %1", room.displayName)
|
||||
visible: roomAccoutnDataVisibleCheck.checked
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Room Account Data for %1 - %2", room.displayName, room.id)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: room.accountDataEventTypes
|
||||
delegate: MobileForm.FormTextDelegate {
|
||||
text: modelData
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer("qrc:/MessageSourceSheet.qml", {
|
||||
"sourceText": room.roomAcountDataJson(text)
|
||||
}, {
|
||||
"title": i18n("Event Source"),
|
||||
"width": Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
}
|
||||
FormCard.FormCard {
|
||||
visible: roomAccoutnDataVisibleCheck.checked
|
||||
|
||||
Repeater {
|
||||
model: room.accountDataEventTypes
|
||||
delegate: FormCard.FormTextDelegate {
|
||||
text: modelData
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer("qrc:/MessageSourceSheet.qml", {
|
||||
"sourceText": room.roomAcountDataJson(text)
|
||||
}, {
|
||||
"title": i18n("Event Source"),
|
||||
"width": Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
|
||||
FormCard.FormHeader {
|
||||
id: stateEventListHeader
|
||||
title: i18n("Room State for %1", room.displayName)
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Layout.fillHeight: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
id: stateEventListHeader
|
||||
title: i18n("Room State for %1", room.displayName)
|
||||
subtitle: room.id
|
||||
}
|
||||
QQC2.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
QQC2.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 20
|
||||
|
||||
ListView {
|
||||
id: stateEventListView
|
||||
clip: true
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
model: StateFilterModel {
|
||||
id: stateEventFilterModel
|
||||
sourceModel: StateModel {
|
||||
id: stateModel
|
||||
room: devtoolsPage.room
|
||||
}
|
||||
ListView {
|
||||
id: stateEventListView
|
||||
clip: true
|
||||
|
||||
model: StateFilterModel {
|
||||
id: stateEventFilterModel
|
||||
sourceModel: StateModel {
|
||||
id: stateModel
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
delegate: MobileForm.FormTextDelegate {
|
||||
text: model.type
|
||||
description: model.stateKey
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer('qrc:/MessageSourceSheet.qml', {
|
||||
sourceText: stateModel.stateEventJson(stateEventFilterModel.mapToSource(stateEventFilterModel.index(model.index, 0)))
|
||||
}, {
|
||||
title: i18n("Event Source"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
}
|
||||
delegate: FormCard.FormTextDelegate {
|
||||
text: model.type
|
||||
description: model.stateKey
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer('qrc:/MessageSourceSheet.qml', {
|
||||
sourceText: stateModel.stateEventJson(stateEventFilterModel.mapToSource(stateEventFilterModel.index(model.index, 0)))
|
||||
}, {
|
||||
title: i18n("Event Source"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,58 +6,50 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
ColumnLayout {
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Server Capabilities")
|
||||
}
|
||||
MobileForm.FormTextDelegate {
|
||||
text: i18n("Can change password")
|
||||
description: Controller.activeConnection.canChangePassword
|
||||
}
|
||||
}
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Default Room Version")
|
||||
}
|
||||
MobileForm.FormTextDelegate {
|
||||
text: Controller.activeConnection.defaultRoomVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
MobileForm.FormCard {
|
||||
Layout.fillWidth: true
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
MobileForm.FormCardHeader {
|
||||
title: i18n("Available Room Versions")
|
||||
}
|
||||
Repeater {
|
||||
model: Controller.getSupportedRoomVersions(room.connection)
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
|
||||
delegate: MobileForm.FormTextDelegate {
|
||||
text: modelData.id
|
||||
contentItem.children: QQC2.Label {
|
||||
text: modelData.status
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
FormCard.FormHeader {
|
||||
title: i18n("Server Capabilities")
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("Can change password")
|
||||
description: Controller.activeConnection.canChangePassword
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18n("Default Room Version")
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
FormCard.FormTextDelegate {
|
||||
text: Controller.activeConnection.defaultRoomVersion
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18n("Available Room Versions")
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Repeater {
|
||||
model: Controller.getSupportedRoomVersions(room.connection)
|
||||
|
||||
delegate: FormCard.FormTextDelegate {
|
||||
text: modelData.id
|
||||
contentItem.children: QQC2.Label {
|
||||
text: modelData.status
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14 as Controls
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtWebView 1.15
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
FormCard.AbstractFormDelegate {
|
||||
background: null
|
||||
contentItem: WebView {
|
||||
id: webview
|
||||
url: "http://localhost:20847"
|
||||
implicitHeight: 500
|
||||
onLoadingChanged: {
|
||||
webview.runJavaScript("document.body.style.background = '" + Kirigami.Theme.backgroundColor + "'")
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
repeat: true
|
||||
running: true
|
||||
interval: 300
|
||||
onTriggered: {
|
||||
if(!webview.visible) {
|
||||
return
|
||||
}
|
||||
webview.runJavaScript("!!grecaptcha ? grecaptcha.getResponse() : \"\"", function(response){
|
||||
if(!webview.visible || !response)
|
||||
return
|
||||
timer.running = false;
|
||||
Registration.recaptchaResponse = response;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Username.qml")
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) emailField.forceActiveFocus()
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: emailField
|
||||
label: i18n("Add an e-mail address:")
|
||||
placeholderText: "user@example.com"
|
||||
onTextChanged: Registration.email = text
|
||||
Keys.onReturnPressed: {
|
||||
if (root.nextAction.enabled) {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormTextDelegate {
|
||||
id: confirmMessage
|
||||
text: i18n("Confirm e-mail address")
|
||||
visible: false
|
||||
description: i18n("A confirmation e-mail has been sent to your address. Please continue here <b>after</b> clicking on the confirmation link in the e-mail")
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: resendButton
|
||||
text: i18nc("@button", "Re-send confirmation e-mail")
|
||||
onClicked: Registration.registerEmail()
|
||||
visible: false
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
enabled: emailField.text.length > 0
|
||||
onTriggered: {
|
||||
if (confirmMessage.visible) {
|
||||
Registration.registerAccount()
|
||||
} else {
|
||||
Registration.registerEmail()
|
||||
confirmMessage.visible = true
|
||||
resendButton.visible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Username.qml")
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
@@ -6,42 +6,56 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) urlField.forceActiveFocus()
|
||||
readonly property var homeserver: customHomeserver.visible ? customHomeserver.text : serverCombo.currentText
|
||||
property bool loading: false
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: urlField
|
||||
label: i18n("Server Url:")
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9]+(:[0-9]+)?/
|
||||
title: i18nc("@title", "Select a Homeserver")
|
||||
|
||||
action: Kirigami.Action {
|
||||
enabled: LoginHelper.homeserverReachable && !customHomeserver.visible || customHomeserver.acceptableInput
|
||||
onTriggered: {
|
||||
// TODO
|
||||
console.log("register todo")
|
||||
}
|
||||
onTextChanged: timer.restart()
|
||||
statusMessage: Registration.status === Registration.ServerNoRegistration ? i18n("Registration is disabled on this server.") : ""
|
||||
Keys.onReturnPressed: {
|
||||
if (root.nextAction.enabled) {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
|
||||
onHomeserverChanged: {
|
||||
LoginHelper.testHomeserver("@user:" + homeserver)
|
||||
}
|
||||
|
||||
Kirigami.FormLayout {
|
||||
Component.onCompleted: Controller.testHomeserver(homeserver)
|
||||
|
||||
QQC2.ComboBox {
|
||||
id: serverCombo
|
||||
|
||||
Kirigami.FormData.label: i18n("Homeserver:")
|
||||
model: ["matrix.org", "kde.org", "tchncs.de", i18n("Other...")]
|
||||
}
|
||||
|
||||
QQC2.TextField {
|
||||
id: customHomeserver
|
||||
|
||||
Kirigami.FormData.label: i18n("Url:")
|
||||
visible: serverCombo.currentIndex === 3
|
||||
onTextChanged: {
|
||||
Controller.testHomeserver(text)
|
||||
}
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9]+(:[0-9]+)?/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 500
|
||||
onTriggered: Registration.homeserver = urlField.text
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
text: Registration.testing ? i18n("Loading") : null
|
||||
enabled: Registration.status > Registration.ServerNoRegistration
|
||||
onTriggered: root.processed("qrc:/Username.qml");
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/LoginRegister.qml")
|
||||
QQC2.Button {
|
||||
id: continueButton
|
||||
text: i18nc("@action:button", "Continue")
|
||||
action: root.action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,16 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
FormCard.FormTextDelegate {
|
||||
Kirigami.LoadingPlaceholder {
|
||||
property var showContinueButton: false
|
||||
property var showBackButton: false
|
||||
|
||||
QQC2.Label {
|
||||
text: i18n("Please wait. This might take a little while.")
|
||||
}
|
||||
FormCard.AbstractFormDelegate {
|
||||
contentItem: QQC2.BusyIndicator {}
|
||||
background: null
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Controller
|
||||
|
||||
@@ -7,34 +7,43 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
id: login
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) matrixIdField.forceActiveFocus()
|
||||
showContinueButton: true
|
||||
showBackButton: false
|
||||
|
||||
title: i18nc("@title", "Login")
|
||||
message: i18n("Enter your Matrix ID")
|
||||
|
||||
Component.onCompleted: {
|
||||
LoginHelper.matrixId = ""
|
||||
}
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: matrixIdField
|
||||
label: i18n("Matrix ID:")
|
||||
placeholderText: "@user:example.org"
|
||||
Accessible.name: i18n("Matrix ID")
|
||||
onTextChanged: {
|
||||
LoginHelper.matrixId = text
|
||||
}
|
||||
Kirigami.FormLayout {
|
||||
QQC2.TextField {
|
||||
id: matrixIdField
|
||||
Kirigami.FormData.label: i18n("Matrix ID:")
|
||||
placeholderText: "@user:matrix.org"
|
||||
Accessible.name: i18n("Matrix ID")
|
||||
onTextChanged: {
|
||||
LoginHelper.matrixId = text
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
root.nextAction.trigger()
|
||||
Component.onCompleted: {
|
||||
matrixIdField.forceActiveFocus()
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
login.action.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
action: Kirigami.Action {
|
||||
text: LoginHelper.isLoggedIn ? i18n("Already logged in") : (LoginHelper.testing && matrixIdField.acceptableInput) ? i18n("Loading…") : i18nc("@action:button", "Continue")
|
||||
onTriggered: {
|
||||
if (LoginHelper.supportsSso && LoginHelper.supportsPassword) {
|
||||
@@ -47,9 +56,4 @@ LoginStep {
|
||||
}
|
||||
enabled: LoginHelper.homeserverReachable
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: {
|
||||
root.processed("qrc:/LoginRegister.qml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,26 +4,28 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
id: loginMethod
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) loginPasswordButton.forceActiveFocus()
|
||||
title: i18n("Login Methods")
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: loginPasswordButton
|
||||
text: i18nc("@action:button", "Login with password")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
QQC2.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Login with password")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/Password.qml")
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: loginSsoButton
|
||||
text: i18nc("@action:button", "Login with single sign-on")
|
||||
QQC2.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Login with single sign-on")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/Sso.qml")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,25 +6,25 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
id: loginRegister
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) loginButton.forceActiveFocus()
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: loginButton
|
||||
text: i18nc("@action:button", "Login")
|
||||
onClicked: root.processed("qrc:/Login.qml")
|
||||
QQC2.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Login")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/Login.qml")
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Register")
|
||||
onClicked: root.processed("qrc:/Homeserver.qml")
|
||||
QQC2.Button {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: i18n("Register")
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||
onClicked: processed("qrc:/Homeserver.qml")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,25 +7,21 @@ import QtQuick.Layouts 1.14
|
||||
|
||||
/// Step for the login/registration flow
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
/// Set to true if the login step does not have any controls. This will ensure that the focus remains on the "continue" button
|
||||
property bool noControls: false
|
||||
property string title: i18n("Welcome")
|
||||
property string message: i18n("Welcome")
|
||||
property bool showContinueButton: false
|
||||
property bool showBackButton: false
|
||||
property bool acceptable: false
|
||||
property string previousUrl: ""
|
||||
|
||||
/// Process this module, this is called by the continue button.
|
||||
/// Should call \sa processed when it finish successfully.
|
||||
property QQC2.Action nextAction: null
|
||||
|
||||
/// Go to the previous module. This is called by the "go back" button.
|
||||
/// If no "go back" button should be shown, this should be null.
|
||||
property QQC2.Action previousAction: null
|
||||
property QQC2.Action action: null
|
||||
|
||||
/// Called when switching to the next step.
|
||||
signal processed(url nextUrl)
|
||||
|
||||
/// Show a message in a banner at the top of the page.
|
||||
signal showMessage(string message)
|
||||
|
||||
/// Clears any error messages currently being shown
|
||||
signal clearError()
|
||||
}
|
||||
|
||||
@@ -6,12 +6,25 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
id: password
|
||||
|
||||
title: i18nc("@title", "Password")
|
||||
message: i18n("Enter your password")
|
||||
showContinueButton: true
|
||||
showBackButton: true
|
||||
previousUrl: LoginHelper.isLoggingIn ? "" : LoginHelper.supportsSso ? "qrc:/LoginMethod.qml" : "qrc:/Login.qml"
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18nc("@action:button", "Login")
|
||||
enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn
|
||||
onTriggered: {
|
||||
LoginHelper.login();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
@@ -20,32 +33,20 @@ LoginStep {
|
||||
}
|
||||
}
|
||||
|
||||
onActiveFocusChanged: if(activeFocus) passwordField.forceActiveFocus()
|
||||
Kirigami.FormLayout {
|
||||
Kirigami.PasswordField {
|
||||
id: passwordField
|
||||
onTextChanged: LoginHelper.password = text
|
||||
enabled: !LoginHelper.isLoggingIn
|
||||
Accessible.name: i18n("Password")
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: passwordField
|
||||
Component.onCompleted: {
|
||||
passwordField.forceActiveFocus()
|
||||
}
|
||||
|
||||
label: i18n("Password:")
|
||||
onTextChanged: LoginHelper.password = text
|
||||
enabled: !LoginHelper.isLoggingIn
|
||||
echoMode: TextInput.Password
|
||||
Accessible.name: i18n("Password")
|
||||
statusMessage: LoginHelper.isInvalidPassword ? i18n("Invalid username or password") : ""
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
root.nextAction.trigger()
|
||||
Keys.onReturnPressed: {
|
||||
password.action.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
text: i18nc("@action:button", "Login")
|
||||
enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn
|
||||
onTriggered: {
|
||||
root.clearError()
|
||||
LoginHelper.login();
|
||||
}
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: processed("qrc:/Login.qml")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) passwordField.forceActiveFocus()
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: passwordField
|
||||
label: i18n("Password:")
|
||||
echoMode: TextInput.Password
|
||||
onTextChanged: Registration.password = text
|
||||
Keys.onReturnPressed: {
|
||||
confirmPasswordField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: confirmPasswordField
|
||||
label: i18n("Confirm Password:")
|
||||
enabled: passwordField.enabled
|
||||
echoMode: TextInput.Password
|
||||
statusMessage: passwordField.text.length === confirmPasswordField.text.length && passwordField.text !== confirmPasswordField.text ? i18n("The passwords do not match.") : ""
|
||||
Keys.onReturnPressed: {
|
||||
if (root.nextAction.enabled) {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
onTriggered: {
|
||||
passwordField.enabled = false
|
||||
Registration.registerAccount()
|
||||
}
|
||||
enabled: passwordField.text === confirmPasswordField.text
|
||||
}
|
||||
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Username.qml")
|
||||
}
|
||||
}
|
||||
@@ -6,38 +6,47 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
noControls: true
|
||||
title: i18nc("@title", "Login")
|
||||
message: i18n("Login with single sign-on")
|
||||
|
||||
Component.onCompleted: LoginHelper.loginWithSso()
|
||||
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onSsoUrlChanged() {
|
||||
UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||
Kirigami.FormLayout {
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onSsoUrlChanged() {
|
||||
UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||
root.showMessage(i18n("Complete the authentication steps in your browser"))
|
||||
loginButton.enabled = true
|
||||
loginButton.text = i18n("Login")
|
||||
}
|
||||
function onConnected() {
|
||||
processed("qrc:/Loading.qml")
|
||||
}
|
||||
}
|
||||
function onConnected() {
|
||||
processed("qrc:/Loading.qml")
|
||||
RowLayout {
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button", "Back")
|
||||
|
||||
onClicked: {
|
||||
module.source = "qrc:/Login.qml"
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
id: loginButton
|
||||
text: i18n("Login")
|
||||
onClicked: {
|
||||
LoginHelper.loginWithSso()
|
||||
loginButton.enabled = false
|
||||
loginButton.text = i18n("Loading…")
|
||||
}
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
Keys.onReturnPressed: clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("Continue the login process in your browser.")
|
||||
}
|
||||
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: processed("qrc:/Login.qml")
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
text: i18nc("@action:button", "Re-open SSO URL")
|
||||
onTriggered: UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
noControls: true
|
||||
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("Terms & Conditions")
|
||||
description: i18n("By continuing with the registration, you agree to the following terms and conditions:")
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Registration.terms
|
||||
delegate: FormCard.FormTextDelegate {
|
||||
text: "<a href=\"" + modelData.url + "\">" + modelData.title + "</a>"
|
||||
onLinkActivated: Qt.openUrlExternally(modelData.url)
|
||||
}
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
onTriggered: {
|
||||
Registration.registerAccount()
|
||||
}
|
||||
}
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Username.qml")
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.12 as QQC2
|
||||
|
||||
import org.kde.kirigami 2.12 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
LoginStep {
|
||||
id: root
|
||||
|
||||
onActiveFocusChanged: if (activeFocus) usernameField.forceActiveFocus()
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: usernameField
|
||||
label: i18n("Username:")
|
||||
placeholderText: "user"
|
||||
onTextChanged: timer.restart()
|
||||
statusMessage: Registration.status === Registration.UsernameTaken ? i18n("Username unavailable") : ""
|
||||
Keys.onReturnPressed: {
|
||||
if (root.nextAction.enabled) {
|
||||
root.nextAction.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 500
|
||||
onTriggered: Registration.username = usernameField.text
|
||||
}
|
||||
|
||||
nextAction: Kirigami.Action {
|
||||
text: Registration.status === Registration.TestingUsername ? i18n("Loading") : null
|
||||
|
||||
onTriggered: root.processed("qrc:/RegisterPassword.qml")
|
||||
enabled: Registration.status === Registration.Ready
|
||||
}
|
||||
|
||||
previousAction: Kirigami.Action {
|
||||
onTriggered: root.processed("qrc:/Homeserver.qml")
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ Components.AlbumMaximizeComponent {
|
||||
|
||||
readonly property var currentJsonSource: model.data(model.index(content.currentIndex, 0), MessageEventModel.SourceRole)
|
||||
|
||||
autoLoad: false
|
||||
|
||||
downloadAction: Components.DownloadAction {
|
||||
id: downloadAction
|
||||
onTriggered: {
|
||||
@@ -89,7 +91,7 @@ Components.AlbumMaximizeComponent {
|
||||
file: parent,
|
||||
mimeType: root.currentMimeType,
|
||||
progressInfo: root.currentProgressInfo,
|
||||
plainText: root.currentPlainText
|
||||
plainMessage: root.currentPlainText
|
||||
});
|
||||
contextMenu.closeFullscreen.connect(root.close)
|
||||
contextMenu.open();
|
||||
|
||||
@@ -375,7 +375,10 @@ ColumnLayout {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
RoomManager.visitUser(root.author.object, "mention")
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: root.author
|
||||
}).open();
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
@@ -456,7 +459,10 @@ ColumnLayout {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
RoomManager.visitUser(root.author.object, "mention")
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: currentRoom,
|
||||
user: root.author
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -599,7 +605,6 @@ ColumnLayout {
|
||||
source: root.jsonSource,
|
||||
eventType: root.delegateType,
|
||||
plainText: root.plainText,
|
||||
htmlText: root.display,
|
||||
});
|
||||
contextMenu.open();
|
||||
}
|
||||
|
||||
@@ -177,13 +177,13 @@ TimelineContainer {
|
||||
|
||||
onDurationChanged: {
|
||||
if (!duration) {
|
||||
root.supportStreaming = false;
|
||||
vid.supportStreaming = false;
|
||||
}
|
||||
}
|
||||
|
||||
onErrorChanged: {
|
||||
if (error != MediaPlayer.NoError) {
|
||||
root.supportStreaming = false;
|
||||
vid.supportStreaming = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ TimelineContainer {
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onTapped: if (root.supportStreaming || root.progressInfo.completed) {
|
||||
onTapped: if (vid.supportStreaming || root.progressInfo.completed) {
|
||||
if (vid.playbackState == MediaPlayer.PlayingState) {
|
||||
vid.pause()
|
||||
} else {
|
||||
|
||||
@@ -17,10 +17,10 @@ QQC2.ScrollView {
|
||||
required property NeoChatRoom currentRoom
|
||||
onCurrentRoomChanged: {
|
||||
roomChanging = true;
|
||||
roomChangingTimer.restart()
|
||||
applicationWindow().hoverLinkIndicator.text = "";
|
||||
messageListView.positionViewAtBeginning();
|
||||
hasScrolledUpBefore = false;
|
||||
roomChanging = true;
|
||||
}
|
||||
property bool roomChanging: false
|
||||
readonly property bool atYEnd: messageListView.atYEnd
|
||||
@@ -47,16 +47,19 @@ QQC2.ScrollView {
|
||||
interactive: Kirigami.Settings.isMobile
|
||||
bottomMargin: Kirigami.Units.largeSpacing + Math.round(Kirigami.Theme.defaultFont.pointSize * 2)
|
||||
|
||||
model: sortedMessageEventModel
|
||||
model: collapseStateProxyModel
|
||||
|
||||
MessageEventModel {
|
||||
id: messageEventModel
|
||||
room: root.currentRoom
|
||||
}
|
||||
|
||||
MessageFilterModel {
|
||||
id: sortedMessageEventModel
|
||||
sourceModel: messageEventModel
|
||||
CollapseStateProxyModel {
|
||||
id: collapseStateProxyModel
|
||||
sourceModel: MessageFilterModel {
|
||||
id: sortedMessageEventModel
|
||||
sourceModel: messageEventModel
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -76,13 +79,6 @@ QQC2.ScrollView {
|
||||
messageEventModel.fetchMore(messageEventModel.index(0, 0));
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: roomChangingTimer
|
||||
interval: 1000
|
||||
onTriggered: {
|
||||
root.roomChanging = false
|
||||
}
|
||||
}
|
||||
onAtYEndChanged: if (!root.roomChanging) {
|
||||
if (atYEnd && root.hasScrolledUpBefore) {
|
||||
if (QQC2.ApplicationWindow.window && (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden)) {
|
||||
@@ -219,6 +215,12 @@ QQC2.ScrollView {
|
||||
FileDelegateContextMenu {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
|
||||
UserDetailDialog {}
|
||||
}
|
||||
|
||||
TypingPane {
|
||||
id: typingPane
|
||||
visible: root.currentRoom && root.currentRoom.usersTyping.length > 0
|
||||
@@ -346,7 +348,7 @@ QQC2.ScrollView {
|
||||
|
||||
MediaMessageFilterModel {
|
||||
id: mediaMessageFilterModel
|
||||
sourceModel: sortedMessageEventModel
|
||||
sourceModel: collapseStateProxyModel
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -436,4 +438,11 @@ QQC2.ScrollView {
|
||||
function positionViewAtBeginning() {
|
||||
messageListView.positionViewAtBeginning()
|
||||
}
|
||||
|
||||
function showUserDetail(user) {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: root.currentRoom,
|
||||
user: root.currentRoom.getUser(user.id),
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
|
||||
property var connection
|
||||
|
||||
title: i18nc("@title", "Deactivate Account")
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Deactivate Account")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18nc("@title", "Warning")
|
||||
description: i18n("Your account will be permanently disabled.\nThis cannot be undone.\nYour Matrix ID will not be available for new accounts.\nYour messages will stay available.")
|
||||
}
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: passwordField
|
||||
label: i18n("Password")
|
||||
echoMode: TextInput.Password
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18n("Deactivate account")
|
||||
icon.name: "emblem-warning"
|
||||
enabled: passwordField.text.length > 0
|
||||
onClicked: {
|
||||
root.connection.deactivateAccount(passwordField.text)
|
||||
root.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ QQC2.Dialog {
|
||||
text: i18n("Sign out")
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
onClicked: {
|
||||
root.connection.logout(true);
|
||||
Controller.logout(root.connection, true);
|
||||
root.close();
|
||||
root.accepted();
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ Kirigami.Dialog {
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
text: user.displayName
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Kirigami.SelectableLabel {
|
||||
|
||||
@@ -24,9 +24,7 @@ Labs.MenuBar {
|
||||
text: i18nc("menu", "Configure NeoChat...")
|
||||
|
||||
shortcut: StandardKey.Preferences
|
||||
onTriggered: pageStack.pushDialogLayer("qrc:/SettingsPage.qml", {
|
||||
connection: Controller.activeConnection
|
||||
}, {
|
||||
onTriggered: pageStack.pushDialogLayer("qrc:/SettingsPage.qml", {}, {
|
||||
title: i18n("Configure")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
id: banSheet
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ Loader {
|
||||
required property string source
|
||||
property string selectedText: ""
|
||||
required property string plainText
|
||||
property string htmlText: undefined
|
||||
|
||||
property list<Kirigami.Action> nestedActions
|
||||
|
||||
@@ -41,20 +40,6 @@ Loader {
|
||||
currentRoom.chatBoxEditId = "";
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu As in 'Forward this message'", "Forward")
|
||||
icon.name: "mail-forward-symbolic"
|
||||
onTriggered: {
|
||||
let page = applicationWindow().pageStack.pushDialogLayer("qrc:/ChooseRoomDialog.qml", {}, {
|
||||
title: i18nc("@title", "Forward Message"),
|
||||
width: Kirigami.Units.gridUnit * 25
|
||||
})
|
||||
page.chosen.connect(function(targetRoomId) {
|
||||
Controller.activeConnection.room(targetRoomId).postHtmlMessage(loadRoot.plainText, loadRoot.htmlText ? loadRoot.htmlText : loadRoot.plainText)
|
||||
page.closeDialog()
|
||||
})
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||
text: i18n("Remove")
|
||||
|
||||
@@ -7,8 +7,6 @@ import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
id: reportSheet
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
import "./RoomList"
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
title: i18nc("@title", "Choose a Room")
|
||||
|
||||
signal chosen(string roomId)
|
||||
|
||||
header: Kirigami.SearchField {
|
||||
onTextChanged: sortModel.filterText = text
|
||||
}
|
||||
|
||||
ListView {
|
||||
model: SortFilterRoomListModel {
|
||||
id: sortModel
|
||||
sourceModel: RoomListModel {
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
}
|
||||
delegate: RoomDelegate {
|
||||
id: roomDelegate
|
||||
filterText: ""
|
||||
onClicked: {
|
||||
root.chosen(roomDelegate.currentRoom.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,41 +2,34 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
import org.kde.kirigamiaddons.settings 1.0 as KirigamiSettings
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
Kirigami.Page {
|
||||
id: devtoolsPage
|
||||
KirigamiSettings.CategorizedSettings {
|
||||
id: root
|
||||
|
||||
property NeoChatRoom room
|
||||
|
||||
title: i18n("Developer Tools")
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
header: QQC2.TabBar {
|
||||
id: tabBar
|
||||
|
||||
QQC2.TabButton {
|
||||
text: qsTr("Room Data")
|
||||
actions: [
|
||||
KirigamiSettings.SettingAction {
|
||||
actionName: "roomData"
|
||||
text: i18n("Room Data")
|
||||
icon.name: "datatype"
|
||||
page: "qrc:/RoomData.qml"
|
||||
initialProperties: {
|
||||
return {
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
},
|
||||
KirigamiSettings.SettingAction {
|
||||
actionName: "serverData"
|
||||
text: i18n("Server Info")
|
||||
icon.name: "network-server-symbolic"
|
||||
page: "qrc:/ServerData.qml"
|
||||
}
|
||||
QQC2.TabButton {
|
||||
text: qsTr("Server Info")
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: swipeView
|
||||
anchors.fill: parent
|
||||
|
||||
currentIndex: tabBar.currentIndex
|
||||
|
||||
RoomData {}
|
||||
ServerData {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -31,15 +31,15 @@ Kirigami.Page {
|
||||
imageDoc.crop(selectionTool.selectionX / ratioX, selectionTool.selectionY / ratioY, selectionTool.selectionWidth / ratioX, selectionTool.selectionHeight / ratioY);
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
actions {
|
||||
left: Kirigami.Action {
|
||||
id: undoAction
|
||||
text: i18nc("@action:button Undo modification", "Undo")
|
||||
icon.name: "edit-undo"
|
||||
onTriggered: imageDoc.undo();
|
||||
visible: imageDoc.edited
|
||||
},
|
||||
Kirigami.Action {
|
||||
}
|
||||
main: Kirigami.Action {
|
||||
id: okAction
|
||||
text: i18nc("@action:button Accept image modification", "Accept")
|
||||
icon.name: "dialog-ok"
|
||||
@@ -54,7 +54,7 @@ Kirigami.Page {
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -122,10 +122,10 @@ Kirigami.ScrollablePage {
|
||||
|
||||
title: i18nc("@title:window", "Add server")
|
||||
|
||||
onOpened: if (!serverUrlField.isValidServer && !opened) {
|
||||
onSheetOpenChanged: if (!serverUrlField.isValidServer && !sheetOpen) {
|
||||
serverField.currentIndex = 0
|
||||
server = serverField.currentValue
|
||||
} else if (opened) {
|
||||
} else if (sheetOpen) {
|
||||
serverUrlField.forceActiveFocus()
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ RowLayout {
|
||||
text: i18n("Create a Space")
|
||||
icon.name: "list-add"
|
||||
onTriggered: {
|
||||
let dialog = createSpaceDialog.createObject(applicationWindow().overlay);
|
||||
let dialog = createSpaceDialog.createObject(root.overlay);
|
||||
dialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import QtQuick.Layouts 1.15
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kitemmodels 1.0
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
|
||||
@@ -72,12 +72,7 @@ QQC2.Control {
|
||||
connection: Controller.activeConnection
|
||||
}
|
||||
}
|
||||
onCountChanged: {
|
||||
root.enabled = count > 0
|
||||
if (!Controller.activeConnection.room(root.selectedSpaceId)) {
|
||||
root.selectedSpaceId = ""
|
||||
}
|
||||
}
|
||||
onCountChanged: root.enabled = count > 0
|
||||
Component.onCompleted: root.enabled = count > 0
|
||||
|
||||
delegate: AvatarTabButton {
|
||||
|
||||
@@ -193,7 +193,7 @@ QQC2.ToolBar {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Label {
|
||||
text: (Controller.activeConnection.label.length > 0 ? (Controller.activeConnection.label + " ") : "") + Controller.activeConnection.localUser.id
|
||||
text: (Controller.activeAccountLabel.length > 0 ? (Controller.activeAccountLabel + " ") : "") + Controller.activeConnection.localUser.id
|
||||
font.pointSize: displayNameLabel.font.pointSize * 0.8
|
||||
opacity: 0.7
|
||||
textFormat: Text.PlainText
|
||||
|
||||
@@ -27,14 +27,6 @@ Kirigami.Page {
|
||||
focus: true
|
||||
padding: 0
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
visible: Kirigami.Settings.isMobile || !applicationWindow().pageStack.wideMode
|
||||
icon.name: "view-right-new"
|
||||
onTriggered: applicationWindow().openRoomDrawer()
|
||||
}
|
||||
]
|
||||
|
||||
KeyNavigation.left: pageStack.get(0)
|
||||
|
||||
onCurrentRoomChanged: {
|
||||
@@ -46,7 +38,7 @@ Kirigami.Page {
|
||||
Connections {
|
||||
target: Controller
|
||||
function onIsOnlineChanged() {
|
||||
if (!Controller.isOnline) {
|
||||
if (true || !Controller.isOnline) {
|
||||
banner.text = i18n("NeoChat is offline. Please check your network connection.");
|
||||
banner.visible = true;
|
||||
banner.type = Kirigami.MessageType.Error;
|
||||
@@ -189,22 +181,7 @@ Kirigami.Page {
|
||||
banner.visible = true;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
function onShowUserDetail(user) {
|
||||
root.showUserDetail(user)
|
||||
}
|
||||
}
|
||||
|
||||
function showUserDetail(user) {
|
||||
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: root.currentRoom,
|
||||
user: root.currentRoom.getUser(user.id),
|
||||
}).open();
|
||||
}
|
||||
|
||||
Component {
|
||||
id: userDetailDialog
|
||||
UserDetailDialog {}
|
||||
timelineViewLoader.item.showUserDetail(user)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,15 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
Kirigami.ScrollablePage {
|
||||
id: welcomePage
|
||||
|
||||
property alias currentStep: module.item
|
||||
|
||||
title: i18n("Welcome")
|
||||
title: module.item.title ?? i18n("Welcome")
|
||||
|
||||
header: QQC2.Control {
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
@@ -26,111 +25,79 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
id: contentCard
|
||||
Component.onCompleted: LoginHelper.init()
|
||||
|
||||
FormCard.AbstractFormDelegate {
|
||||
contentItem: Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 16
|
||||
}
|
||||
background: Item {}
|
||||
onActiveFocusChanged: if (activeFocus) module.item.forceActiveFocus()
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormTextDelegate {
|
||||
id: welcomeMessage
|
||||
text: AccountRegistry.accountCount > 0 ? i18n("Log in to a different account or create a new account.") : i18n("Welcome to NeoChat! Continue by logging in or creating a new account.")
|
||||
Connections {
|
||||
target: Controller
|
||||
function onInitiated() {
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: welcomeMessage
|
||||
ColumnLayout {
|
||||
Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 16
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pixelSize: 25
|
||||
text: module.item.message ?? module.item.title ?? i18n("Welcome to Matrix")
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: module
|
||||
Layout.fillWidth: true
|
||||
source: "qrc:/LoginRegister.qml"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
source: "qrc:/Login.qml"
|
||||
onSourceChanged: {
|
||||
headerMessage.visible = false
|
||||
headerMessage.text = ""
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Connections {
|
||||
id: stepConnections
|
||||
target: currentStep
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button", "Back")
|
||||
|
||||
function onProcessed(nextUrl) {
|
||||
module.source = nextUrl;
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
if (!module.item.noControls) {
|
||||
module.item.forceActiveFocus()
|
||||
} else {
|
||||
continueButton.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
function onShowMessage(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Information;
|
||||
}
|
||||
function onClearError() {
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
enabled: welcomePage.currentStep.previousUrl !== ""
|
||||
visible: welcomePage.currentStep.showBackButton
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: {
|
||||
module.source = welcomePage.currentStep.previousUrl
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: Registration
|
||||
function onNextStepChanged() {
|
||||
if (Registration.nextStep === "m.login.recaptcha") {
|
||||
stepConnections.onProcessed("qrc:/Captcha.qml")
|
||||
}
|
||||
if (Registration.nextStep === "m.login.terms") {
|
||||
stepConnections.onProcessed("qrc:/Terms.qml")
|
||||
}
|
||||
if (Registration.nextStep === "m.login.email.identity") {
|
||||
stepConnections.onProcessed("qrc:/Email.qml")
|
||||
}
|
||||
if (Registration.nextStep === "loading") {
|
||||
stepConnections.onProcessed("qrc:/Loading.qml")
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = message.length > 0;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
id: continueButton
|
||||
enabled: welcomePage.currentStep.acceptable
|
||||
visible: welcomePage.currentStep.showContinueButton
|
||||
action: welcomePage.currentStep.action
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator {
|
||||
below: continueButton
|
||||
}
|
||||
Connections {
|
||||
target: currentStep
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: continueButton
|
||||
text: root.currentStep.nextAction && root.currentStep.nextAction.text ? root.currentStep.nextAction.text : i18nc("@action:button", "Continue")
|
||||
visible: root.currentStep.nextAction
|
||||
onClicked: root.currentStep.nextAction.trigger()
|
||||
icon.name: "arrow-right"
|
||||
enabled: root.currentStep.nextAction ? root.currentStep.nextAction.enabled : false
|
||||
function onProcessed(nextUrl) {
|
||||
module.source = nextUrl;
|
||||
}
|
||||
function onShowMessage(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Information;
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Go back")
|
||||
visible: root.currentStep.previousAction
|
||||
onClicked: root.currentStep.previousAction.trigger()
|
||||
icon.name: "arrow-left"
|
||||
enabled: root.currentStep.previousAction ? root.currentStep.previousAction.enabled : false
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
LoginHelper.init()
|
||||
module.item.forceActiveFocus()
|
||||
Registration.username = ""
|
||||
Registration.password = ""
|
||||
Registration.email = ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,17 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
onClicked: {
|
||||
RoomManager.visitUser(room.getUser(room.directChatRemoteUser.id).object, "mention")
|
||||
const popup = userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||
room: room,
|
||||
user: room.getUser(room.directChatRemoteUser.id),
|
||||
})
|
||||
popup.closed.connect(() => {
|
||||
userListItem.highlighted = false
|
||||
})
|
||||
if (roomDrawer.modal) {
|
||||
roomDrawer.close()
|
||||
}
|
||||
popup.open()
|
||||
}
|
||||
|
||||
contentItem: KirigamiComponents.Avatar {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user