Compare commits
2 Commits
v24.11.80
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80ebe71f6a | ||
|
|
02578dd38f |
@@ -110,7 +110,7 @@
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/quotient-im/libQuotient.git",
|
||||
"branch": "dev",
|
||||
"branch": "0.8.x",
|
||||
"disable-submodules": true
|
||||
}
|
||||
],
|
||||
|
||||
@@ -9,7 +9,7 @@ cmake_minimum_required(VERSION 3.16)
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "80")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
@@ -107,7 +107,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
||||
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(QuotientQt6 0.9)
|
||||
find_package(QuotientQt6 0.8.2)
|
||||
set_package_properties(QuotientQt6 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
DESCRIPTION "Qt wrapper around Matrix API"
|
||||
@@ -115,6 +115,11 @@ set_package_properties(QuotientQt6 PROPERTIES
|
||||
PURPOSE "Talk with matrix server"
|
||||
)
|
||||
|
||||
if (NOT TARGET Olm::Olm)
|
||||
message(FATAL_ERROR "NeoChat requires Quotient with the E2EE feature enabled")
|
||||
endif()
|
||||
|
||||
|
||||
find_package(cmark)
|
||||
set_package_properties(cmark PROPERTIES
|
||||
TYPE REQUIRED
|
||||
|
||||
@@ -11,7 +11,7 @@ A Qt/QML based Matrix client.
|
||||
|
||||
<a href='https://matrix.org'><img src='https://matrix.org/docs/legacy/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
|
||||
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
|
||||
<a href='https://snapcraft.io/neochat'><img width='190px' alt='Download on the Snap Store' src='https://apps.kde.org/store_badges/snapstore/en.svg'/></a>
|
||||
<a href='https://snapcraft.io/neochat'><img width='190px' alt='Download on the Snap Store' src='https://snapcraft.io/static/images/badges/en/snap-store-black.svg'/></a>
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -5,26 +5,6 @@
|
||||
"!room_id_1234:localhost:1234": {
|
||||
"state": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"m.federate": true,
|
||||
"predecessor": {
|
||||
"event_id": "$something:example.org",
|
||||
"room_id": "!oldroom:example.org"
|
||||
},
|
||||
"room_version": "11"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "",
|
||||
"type": "m.room.create",
|
||||
"unsigned": {
|
||||
"age": 1234,
|
||||
"membership": "join"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "m.room.member",
|
||||
"state_key": "@user:localhost:1234",
|
||||
@@ -46,26 +26,6 @@
|
||||
},
|
||||
"timeline": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"m.federate": true,
|
||||
"predecessor": {
|
||||
"event_id": "$something:example.org",
|
||||
"room_id": "!oldroom:example.org"
|
||||
},
|
||||
"room_version": "11"
|
||||
},
|
||||
"event_id": "$143273582443PhrSn:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "",
|
||||
"type": "m.room.create",
|
||||
"unsigned": {
|
||||
"age": 1234,
|
||||
"membership": "join"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "m.room.message",
|
||||
"sender": "@user:localhost:1234",
|
||||
|
||||
@@ -53,6 +53,12 @@ ecm_add_test(
|
||||
TEST_NAME messageeventmodeltest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
actionshandlertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME actionshandlertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
windowcontrollertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
|
||||
41
autotests/actionshandlertest.cpp
Normal file
41
autotests/actionshandlertest.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QTest>
|
||||
|
||||
#include "actionshandler.h"
|
||||
#include "chatbarcache.h"
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
class ActionsHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Quotient::Connection *connection = Quotient::Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
|
||||
private Q_SLOTS:
|
||||
void nullObject();
|
||||
};
|
||||
|
||||
void ActionsHandlerTest::nullObject()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(nullptr, nullptr);
|
||||
|
||||
auto chatBarCache = new ChatBarCache(this);
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(nullptr, chatBarCache);
|
||||
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(room, nullptr);
|
||||
|
||||
// The final one should throw no warning so we make sure.
|
||||
QTest::failOnWarning("ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
ActionsHandler::handleMessageEvent(room, chatBarCache);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(ActionsHandlerTest)
|
||||
#include "actionshandlertest.moc"
|
||||
@@ -54,11 +54,9 @@
|
||||
<summary xml:lang="ar">دردش على ماتركس</summary>
|
||||
<summary xml:lang="ca">Xat a Matrix</summary>
|
||||
<summary xml:lang="ca-valencia">Xat a Matrix</summary>
|
||||
<summary xml:lang="en-GB">Chat on Matrix</summary>
|
||||
<summary xml:lang="es">Charle en Matrix</summary>
|
||||
<summary xml:lang="eu">Berriketa Matrix-en</summary>
|
||||
<summary xml:lang="fr">Discuter sur Matrix</summary>
|
||||
<summary xml:lang="gl">Charlar en Matrix</summary>
|
||||
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
|
||||
<summary xml:lang="it">Chat su Matrix</summary>
|
||||
<summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary>
|
||||
|
||||
@@ -87,19 +87,47 @@ GenericName[uk]=Клієнт Matrix
|
||||
GenericName[x-test]=xxMatrix Clientxx
|
||||
GenericName[zh_CN]=Matrix 客户端
|
||||
GenericName[zh_TW]=Matrix 用戶端
|
||||
Comment=Chat on Matrix
|
||||
Comment[ca]=Xat a Matrix
|
||||
Comment[ca@valencia]=Xat a Matrix
|
||||
Comment[es]=Chat en Matrix
|
||||
Comment[eu]=Berriketa Matrix-en
|
||||
Comment[fr]=Clavarder sur Matrix
|
||||
Comment[gl]=Charle en Matrix
|
||||
Comment[hu]=Csevegés Matrixon
|
||||
Comment[it]= su Matrix
|
||||
Comment[pl]=Rozmawiaj na Matriksie
|
||||
Comment[sl]=Klepet na Matrixu
|
||||
Comment[tr]=Matrix Üzerinde Sohbet Et
|
||||
Comment[uk]=Спілкування у Matrix
|
||||
Comment=Client for the Matrix protocol
|
||||
Comment[ar]=عميل لميفاق ماتركس
|
||||
Comment[az]=Matrix protokolu üçün müştəri
|
||||
Comment[ca]=Client per al protocol Matrix
|
||||
Comment[ca@valencia]=Client per al protocol Matrix
|
||||
Comment[de]=Programm für das Matrix-Protokoll
|
||||
Comment[el]=Πελάτης για το πρωτόκολλο Matrix
|
||||
Comment[en_GB]=Client for the Matrix protocol
|
||||
Comment[eo]=Kliento por la Matrix-protokolo
|
||||
Comment[es]=Cliente para el protocolo Matrix
|
||||
Comment[eu]=Matrix protokolorako bezeroa
|
||||
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
||||
Comment[fr]=Client pour le protocole « Matrix »
|
||||
Comment[gl]=Cliente para o protocolo Matrix.
|
||||
Comment[he]=לקוח לפרוטוקול Matrix
|
||||
Comment[hu]=Kliens a Matrix protokollhoz
|
||||
Comment[ia]=Cliente per le protocollo de Matrix
|
||||
Comment[id]=Klien untuk protokol Matrix
|
||||
Comment[ie]=Un cliente del protocol Matrix
|
||||
Comment[it]=Client per il protocollo Matrix
|
||||
Comment[ka]=კლიენტი Matrix-ის პროტოკოლისთვის
|
||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||
Comment[lt]=Matrix protokolo kliento programa
|
||||
Comment[lv]=Klients „Matrix“ protokolam
|
||||
Comment[nl]=Client voor het Matrix-protocol
|
||||
Comment[nn]=Klient for Matrix-protokollen
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
Comment[pl]=Program obsługi protokołu Matriksa
|
||||
Comment[pt]=Cliente para o protocolo Matrix
|
||||
Comment[pt_BR]=Cliente para o protocolo Matrix
|
||||
Comment[ro]=Client pentru protocolul Matrix
|
||||
Comment[ru]=Клиент для протокола Matrix
|
||||
Comment[sk]=Klient protokolu Matrix
|
||||
Comment[sl]=Odjemalec za protokol Matrix
|
||||
Comment[sv]=Klient för protokollet Matrix
|
||||
Comment[ta]=Matrix நெறிமுறைக்கான வாங்கி
|
||||
Comment[tr]=Matrix protokolü için istemci
|
||||
Comment[uk]=Клієнт протоколу Matrix
|
||||
Comment[x-test]=xxClient for the Matrix protocolxx
|
||||
Comment[zh_CN]=为 Matrix 协议打造的客户端
|
||||
Comment[zh_TW]=Matrix 通訊協定的用戶端
|
||||
MimeType=x-scheme-handler/matrix;
|
||||
Exec=neochat %u
|
||||
Terminal=false
|
||||
|
||||
585
po/ar/neochat.po
585
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
587
po/az/neochat.po
587
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
870
po/ca/neochat.po
870
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
576
po/cs/neochat.po
576
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
584
po/da/neochat.po
584
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
585
po/de/neochat.po
585
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
587
po/el/neochat.po
587
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
585
po/eo/neochat.po
585
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
584
po/es/neochat.po
584
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
586
po/eu/neochat.po
586
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
585
po/fi/neochat.po
585
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
588
po/fr/neochat.po
588
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
631
po/gl/neochat.po
631
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
634
po/hu/neochat.po
634
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
587
po/ia/neochat.po
587
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
587
po/id/neochat.po
587
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
587
po/ie/neochat.po
587
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
586
po/it/neochat.po
586
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
567
po/ja/neochat.po
567
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
585
po/ka/neochat.po
585
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
585
po/ko/neochat.po
585
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
567
po/lt/neochat.po
567
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
585
po/lv/neochat.po
585
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
589
po/nl/neochat.po
589
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
582
po/nn/neochat.po
582
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
587
po/pa/neochat.po
587
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
584
po/pl/neochat.po
584
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
587
po/pt/neochat.po
587
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
692
po/ru/neochat.po
692
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
587
po/sk/neochat.po
587
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
582
po/sl/neochat.po
582
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
585
po/sv/neochat.po
585
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
585
po/ta/neochat.po
585
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
792
po/tr/neochat.po
792
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
584
po/uk/neochat.po
584
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
@@ -92,7 +92,7 @@ parts:
|
||||
- olm
|
||||
- qtkeychain
|
||||
source: https://github.com/quotient-im/libQuotient.git
|
||||
source-tag: 0.9.0
|
||||
source-tag: 0.8.2
|
||||
source-depth: 1
|
||||
plugin: cmake
|
||||
build-packages:
|
||||
|
||||
@@ -10,6 +10,8 @@ endif()
|
||||
add_library(neochat STATIC
|
||||
controller.cpp
|
||||
controller.h
|
||||
actionshandler.cpp
|
||||
actionshandler.h
|
||||
models/emojimodel.cpp
|
||||
models/emojimodel.h
|
||||
emojitones.cpp
|
||||
|
||||
144
src/actionshandler.cpp
Normal file
144
src/actionshandler.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "actionshandler.h"
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
void ActionsHandler::handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache)
|
||||
{
|
||||
if (room == nullptr || chatBarCache == nullptr) {
|
||||
qWarning() << "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chatBarCache->attachmentPath().isEmpty()) {
|
||||
QUrl url(chatBarCache->attachmentPath());
|
||||
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||
room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
|
||||
chatBarCache->setAttachmentPath({});
|
||||
chatBarCache->setText({});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto handledText = handleMentions(chatBarCache);
|
||||
const auto result = handleQuickEdit(room, handledText);
|
||||
if (!result) {
|
||||
handleMessage(room, handledText, chatBarCache);
|
||||
}
|
||||
}
|
||||
|
||||
QString ActionsHandler::handleMentions(ChatBarCache *chatBarCache)
|
||||
{
|
||||
const auto mentions = chatBarCache->mentions();
|
||||
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
|
||||
return a.cursor.anchor() > b.cursor.anchor();
|
||||
});
|
||||
|
||||
auto handledText = chatBarCache->text();
|
||||
for (const auto &mention : *mentions) {
|
||||
if (mention.text.isEmpty() || mention.id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
handledText = handledText.replace(mention.cursor.anchor(),
|
||||
mention.cursor.position() - mention.cursor.anchor(),
|
||||
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
|
||||
}
|
||||
mentions->clear();
|
||||
|
||||
return handledText;
|
||||
}
|
||||
|
||||
bool ActionsHandler::handleQuickEdit(NeoChatRoom *room, const QString &handledText)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
|
||||
auto match = sed.match(handledText);
|
||||
if (match.hasMatch()) {
|
||||
const QString regex = match.captured(1);
|
||||
const QString replacement = match.captured(2).toHtmlEscaped();
|
||||
const QString flags = match.captured(3);
|
||||
|
||||
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
|
||||
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event->senderId() == room->localMember().id() && event->has<EventContent::TextContent>()) {
|
||||
#else
|
||||
if (event->senderId() == room->localMember().id() && event->hasTextContent()) {
|
||||
#endif
|
||||
QString originalString;
|
||||
if (event->content()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
originalString = event->get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
|
||||
#endif
|
||||
} else {
|
||||
originalString = event->plainBody();
|
||||
}
|
||||
if (flags == "/g"_L1) {
|
||||
room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
|
||||
} else {
|
||||
room->postHtmlMessage(handledText,
|
||||
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
||||
event->msgtype(),
|
||||
{},
|
||||
event->id());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActionsHandler::handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto messageType = RoomMessageEvent::MsgType::Text;
|
||||
|
||||
if (handledText.startsWith(QLatin1Char('/'))) {
|
||||
for (const auto &action : ActionsModel::instance().allActions()) {
|
||||
if (handledText.indexOf(action.prefix) == 1
|
||||
&& (handledText.indexOf(" "_ls) == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
|
||||
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), room, chatBarCache);
|
||||
if (action.messageType.has_value()) {
|
||||
messageType = *action.messageType;
|
||||
}
|
||||
if (action.messageAction) {
|
||||
break;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextHandler textHandler;
|
||||
textHandler.setData(handledText);
|
||||
handledText = textHandler.handleSendText();
|
||||
|
||||
if (handledText.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
room->postMessage(chatBarCache->text(), handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
|
||||
}
|
||||
|
||||
#include "moc_actionshandler.cpp"
|
||||
43
src/actionshandler.h
Normal file
43
src/actionshandler.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
class ChatBarCache;
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ActionsHandler
|
||||
*
|
||||
* This class contains functions to handle chat messages ready for posting to a room.
|
||||
*
|
||||
* Everything that needs to be done to prepare the message for posting in a room
|
||||
* including:
|
||||
* - File handling
|
||||
* - User mentions
|
||||
* - Quick edits
|
||||
* - Chat actions
|
||||
* - Custom emojis
|
||||
*
|
||||
* @note A chat action is a message starting with /, resulting in something other
|
||||
* than a normal message being sent (e.g. /me, /join).
|
||||
*
|
||||
* @sa ActionsModel, NeoChatRoom
|
||||
*/
|
||||
class ActionsHandler
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Pre-process text and send message event.
|
||||
*/
|
||||
static void handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache);
|
||||
|
||||
private:
|
||||
static QString handleMentions(ChatBarCache *chatBarCache);
|
||||
static bool handleQuickEdit(NeoChatRoom *room, const QString &handledText);
|
||||
|
||||
static void handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache);
|
||||
};
|
||||
@@ -251,22 +251,20 @@ QQC2.Control {
|
||||
}
|
||||
}
|
||||
Keys.onEnterPressed: event => {
|
||||
const controlIsPressed = event.modifiers & Qt.ControlModifier;
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.complete();
|
||||
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile || NeoChatConfig.sendMessageWith === 1 && !controlIsPressed || NeoChatConfig.sendMessageWith === 0 && controlIsPressed) {
|
||||
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile) {
|
||||
textField.insert(cursorPosition, "\n");
|
||||
} else if (NeoChatConfig.sendMessageWith === 0 && !controlIsPressed || NeoChatConfig.sendMessageWith === 1 && controlIsPressed) {
|
||||
} else {
|
||||
_private.postMessage();
|
||||
}
|
||||
}
|
||||
Keys.onReturnPressed: event => {
|
||||
const controlIsPressed = event.modifiers & Qt.ControlModifier;
|
||||
if (completionMenu.visible) {
|
||||
completionMenu.complete();
|
||||
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile || NeoChatConfig.sendMessageWith === 1 && !controlIsPressed || NeoChatConfig.sendMessageWith === 0 && controlIsPressed) {
|
||||
} else if (event.modifiers & Qt.ShiftModifier || Kirigami.Settings.isMobile) {
|
||||
textField.insert(cursorPosition, "\n");
|
||||
} else if (NeoChatConfig.sendMessageWith === 0 && !controlIsPressed || NeoChatConfig.sendMessageWith === 1 && controlIsPressed) {
|
||||
} else {
|
||||
_private.postMessage();
|
||||
}
|
||||
}
|
||||
@@ -407,6 +405,7 @@ QQC2.Control {
|
||||
repeatTimer.stop();
|
||||
root.currentRoom.markAllMessagesAsRead();
|
||||
textField.clear();
|
||||
_private.chatBarCache.clearRelations();
|
||||
messageSent();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
#include "actionshandler.h"
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "eventhandler.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
ChatBarCache::ChatBarCache(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -30,37 +29,6 @@ void ChatBarCache::setText(const QString &text)
|
||||
Q_EMIT textChanged();
|
||||
}
|
||||
|
||||
QString ChatBarCache::sendText() const
|
||||
{
|
||||
if (!attachmentPath().isEmpty()) {
|
||||
QUrl url(attachmentPath());
|
||||
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||
return text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : text();
|
||||
}
|
||||
|
||||
return formatMentions();
|
||||
}
|
||||
|
||||
QString ChatBarCache::formatMentions() const
|
||||
{
|
||||
auto mentions = m_mentions;
|
||||
std::sort(mentions.begin(), mentions.end(), [](const auto &a, const auto &b) {
|
||||
return a.cursor.anchor() > b.cursor.anchor();
|
||||
});
|
||||
|
||||
auto formattedText = text();
|
||||
for (const auto &mention : mentions) {
|
||||
if (mention.text.isEmpty() || mention.id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
formattedText = formattedText.replace(mention.cursor.anchor(),
|
||||
mention.cursor.position() - mention.cursor.anchor(),
|
||||
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
|
||||
}
|
||||
|
||||
return formattedText;
|
||||
}
|
||||
|
||||
bool ChatBarCache::isReplying() const
|
||||
{
|
||||
return m_relationType == Reply && !m_relationId.isEmpty();
|
||||
@@ -300,35 +268,7 @@ void ChatBarCache::postMessage()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!attachmentPath().isEmpty()) {
|
||||
room->uploadFile(QUrl(attachmentPath()), sendText());
|
||||
clearCache();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = ActionsModel::handleAction(room, this);
|
||||
if (!result.first.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextHandler textHandler;
|
||||
textHandler.setData(*std::get<std::optional<QString>>(result));
|
||||
const auto sendText = textHandler.handleSendText();
|
||||
|
||||
if (sendText.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
room->postMessage(text(), sendText, *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), replyId(), editId(), threadId());
|
||||
clearCache();
|
||||
}
|
||||
|
||||
void ChatBarCache::clearCache()
|
||||
{
|
||||
setText({});
|
||||
m_mentions.clear();
|
||||
m_savedText = QString();
|
||||
clearRelations();
|
||||
ActionsHandler::handleMessageEvent(room, this);
|
||||
}
|
||||
|
||||
#include "moc_chatbarcache.cpp"
|
||||
|
||||
@@ -153,7 +153,6 @@ public:
|
||||
explicit ChatBarCache(QObject *parent = nullptr);
|
||||
|
||||
QString text() const;
|
||||
QString sendText() const;
|
||||
void setText(const QString &text);
|
||||
|
||||
bool isReplying() const;
|
||||
@@ -216,8 +215,6 @@ Q_SIGNALS:
|
||||
|
||||
private:
|
||||
QString m_text = QString();
|
||||
QString formatMentions() const;
|
||||
|
||||
QString m_relationId = QString();
|
||||
RelationType m_relationType = RelationType::None;
|
||||
QString m_threadId = QString();
|
||||
@@ -226,6 +223,4 @@ private:
|
||||
QString m_savedText;
|
||||
|
||||
QPointer<MessageContentModel> m_relationContentModel;
|
||||
|
||||
void clearCache();
|
||||
};
|
||||
|
||||
@@ -63,7 +63,11 @@ Controller::Controller(QObject *parent)
|
||||
});
|
||||
} else {
|
||||
auto c = new NeoChatConnection(this);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
c->assumeIdentity(QStringLiteral("@user:localhost:1234"), QStringLiteral("device_1234"), QStringLiteral("token_1234"));
|
||||
#else
|
||||
c->assumeIdentity(QStringLiteral("@user:localhost:1234"), QStringLiteral("token_1234"));
|
||||
#endif
|
||||
connect(c, &Connection::connected, this, [c, this]() {
|
||||
m_accountRegistry.add(c);
|
||||
c->syncLoop();
|
||||
@@ -226,7 +230,11 @@ void Controller::invokeLogin()
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
});
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection->assumeIdentity(account.userId(), account.deviceId(), accessToken);
|
||||
#else
|
||||
connection->assumeIdentity(account.userId(), accessToken);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -433,7 +441,11 @@ void Controller::removeConnection(const QString &userId)
|
||||
|
||||
bool Controller::csSupported() const
|
||||
{
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::revertToDefaultConfig()
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
@@ -24,7 +23,7 @@ ColumnLayout {
|
||||
model: root.connection.accountDataEventTypes
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: modelData
|
||||
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
sourceText: root.connection.accountDataJsonString(modelData)
|
||||
}, {
|
||||
title: i18nc("@title:window", "Event Source"),
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
@@ -48,7 +47,7 @@ ColumnLayout {
|
||||
model: root.room.accountDataEventTypes
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: modelData
|
||||
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
sourceText: root.room.roomAcountDataJson(text)
|
||||
}, {
|
||||
title: i18n("Event Source"),
|
||||
@@ -78,7 +77,7 @@ ColumnLayout {
|
||||
if (model.eventCount === 1) {
|
||||
openEventSource(model.type, model.stateKey);
|
||||
} else {
|
||||
root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
|
||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
|
||||
room: root.room,
|
||||
eventType: model.type
|
||||
}, {
|
||||
@@ -90,7 +89,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
function openEventSource(type: string, stateKey: string): void {
|
||||
onClicked: root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
||||
model: stateModel,
|
||||
allowEdit: true,
|
||||
room: root.room,
|
||||
|
||||
@@ -225,10 +225,18 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
{
|
||||
QString body;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (event.hasFileContent()) {
|
||||
#endif
|
||||
// if filename is given or body is equal to filename,
|
||||
// then body is a caption
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString filename = event.get<EventContent::FileContent>()->originalName;
|
||||
#else
|
||||
QString filename = event.content()->fileInfo()->originalName;
|
||||
#endif
|
||||
QString body = event.plainBody();
|
||||
if (filename.isEmpty() || filename == body) {
|
||||
return QString();
|
||||
@@ -236,8 +244,13 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
return body;
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::TextContent>() && event.content()) {
|
||||
body = event.get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
if (event.hasTextContent() && event.content()) {
|
||||
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
||||
#endif
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
}
|
||||
@@ -464,8 +477,13 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
{
|
||||
TextHandler textHandler;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::FileContent>()) {
|
||||
QString fileCaption = event.get<EventContent::FileContent>()->originalName;
|
||||
#else
|
||||
if (event.hasFileContent()) {
|
||||
QString fileCaption = event.content()->fileInfo()->originalName;
|
||||
#endif
|
||||
if (fileCaption.isEmpty()) {
|
||||
fileCaption = event.plainBody();
|
||||
} else if (fileCaption != event.plainBody()) {
|
||||
@@ -476,8 +494,13 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
}
|
||||
|
||||
QString body;
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event.has<EventContent::TextContent>() && event.content()) {
|
||||
body = event.get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
if (event.hasTextContent() && event.content()) {
|
||||
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
||||
#endif
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
}
|
||||
@@ -692,15 +715,28 @@ QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Qu
|
||||
// Get the file info for the event.
|
||||
if (event->is<RoomMessageEvent>()) {
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (!roomMessageEvent->has<EventContent::FileContentBase>()) {
|
||||
#else
|
||||
if (!roomMessageEvent->hasFileContent()) {
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const auto content = roomMessageEvent->get<EventContent::FileContentBase>();
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, content.get(), eventId, false, false);
|
||||
#else
|
||||
const auto content = static_cast<const EventContent::FileContent *>(roomMessageEvent->content());
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, content, eventId, false, false);
|
||||
#endif
|
||||
// if filename isn't specifically given, it is in body
|
||||
// https://spec.matrix.org/latest/client-server-api/#mfile
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["filename"_ls] = content->commonInfo().originalName.isEmpty() ? roomMessageEvent->plainBody() : content->commonInfo().originalName;
|
||||
#else
|
||||
mediaInfo["filename"_ls] = (content->fileInfo()->originalName.isEmpty()) ? roomMessageEvent->plainBody() : content->fileInfo()->originalName;
|
||||
#endif
|
||||
|
||||
return mediaInfo;
|
||||
} else if (event->is<StickerEvent>()) {
|
||||
@@ -714,7 +750,11 @@ QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Qu
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const Quotient::EventContent::FileContentBase *fileContent,
|
||||
#else
|
||||
const Quotient::EventContent::TypedBase *fileContent,
|
||||
#endif
|
||||
const QString &eventId,
|
||||
bool isThumbnail,
|
||||
bool isSticker)
|
||||
@@ -722,10 +762,18 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
// Get the mxc URL for the media.
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (!fileContent->url().isValid() || fileContent->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
#else
|
||||
if (!fileContent->fileInfo()->url().isValid() || fileContent->fileInfo()->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
#endif
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QUrl source = room->makeMediaUrl(eventId, fileContent->url());
|
||||
#else
|
||||
QUrl source = room->makeMediaUrl(eventId, fileContent->fileInfo()->url());
|
||||
#endif
|
||||
|
||||
if (source.isValid()) {
|
||||
mediaInfo["source"_ls] = source;
|
||||
@@ -742,15 +790,25 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
mediaInfo["mimeIcon"_ls] = mimeType.iconName();
|
||||
|
||||
// Add media size if available.
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["size"_ls] = fileContent->commonInfo().payloadSize;
|
||||
#else
|
||||
mediaInfo["size"_ls] = static_cast<const EventContent::FileContent *>(fileContent)->fileInfo()->payloadSize;
|
||||
#endif
|
||||
|
||||
mediaInfo["isSticker"_ls] = isSticker;
|
||||
|
||||
// Add parameter depending on media type.
|
||||
if (mimeType.name().contains(QStringLiteral("image"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileContent)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
mediaInfo["width"_ls] = castInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = castInfo->imageSize.height();
|
||||
#else
|
||||
const auto imageInfo = static_cast<const EventContent::ImageInfo *>(castInfo->fileInfo());
|
||||
mediaInfo["width"_ls] = imageInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = imageInfo->imageSize.height();
|
||||
#endif
|
||||
|
||||
// TODO: Images in certain formats (e.g. WebP) will be erroneously marked as animated, even if they are static.
|
||||
mediaInfo["animated"_ls] = QMovie::supportedFormats().contains(mimeType.preferredSuffix().toUtf8());
|
||||
|
||||
@@ -290,7 +290,11 @@ private:
|
||||
|
||||
static QVariantMap getMediaInfoForEvent(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
QVariantMap static getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
const Quotient::EventContent::FileContentBase *fileContent,
|
||||
#else
|
||||
const Quotient::EventContent::TypedBase *fileContent,
|
||||
#endif
|
||||
const QString &eventId,
|
||||
bool isThumbnail = false,
|
||||
bool isSticker = false);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "imagepackevent.h"
|
||||
#include <QJsonObject>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -10,10 +11,10 @@ ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
||||
{
|
||||
if (json.contains(QStringLiteral("pack"))) {
|
||||
pack = ImagePackEventContent::Pack{
|
||||
fromJson<std::optional<QString>>(json["pack"_ls].toObject()["display_name"_ls]),
|
||||
fromJson<std::optional<QUrl>>(json["pack"_ls].toObject()["avatar_url"_ls]),
|
||||
fromJson<std::optional<QStringList>>(json["pack"_ls].toObject()["usage"_ls]),
|
||||
fromJson<std::optional<QString>>(json["pack"_ls].toObject()["attribution"_ls]),
|
||||
fromJson<Omittable<QString>>(json["pack"_ls].toObject()["display_name"_ls]),
|
||||
fromJson<Omittable<QUrl>>(json["pack"_ls].toObject()["avatar_url"_ls]),
|
||||
fromJson<Omittable<QStringList>>(json["pack"_ls].toObject()["usage"_ls]),
|
||||
fromJson<Omittable<QString>>(json["pack"_ls].toObject()["attribution"_ls]),
|
||||
};
|
||||
} else {
|
||||
pack = std::nullopt;
|
||||
@@ -30,9 +31,9 @@ ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
||||
images += ImagePackImage{
|
||||
k,
|
||||
fromJson<QUrl>(json["images"_ls][k]["url"_ls].toString()),
|
||||
fromJson<std::optional<QString>>(json["images"_ls][k]["body"_ls]),
|
||||
fromJson<Omittable<QString>>(json["images"_ls][k]["body"_ls]),
|
||||
info,
|
||||
fromJson<std::optional<QStringList>>(json["images"_ls][k]["usage"_ls]),
|
||||
fromJson<Omittable<QStringList>>(json["images"_ls][k]["usage"_ls]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/e2ee/sssshandler.h>
|
||||
#include <Quotient/keyimport.h>
|
||||
#include <Quotient/keyverificationsession.h>
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
#include <Quotient/keyimport.h>
|
||||
#endif
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
@@ -40,9 +43,11 @@ struct ForeignSSSSHandler {
|
||||
QML_NAMED_ELEMENT(SSSSHandler)
|
||||
};
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
struct ForeignKeyImport {
|
||||
Q_GADGET
|
||||
QML_SINGLETON
|
||||
QML_FOREIGN(Quotient::KeyImport)
|
||||
QML_NAMED_ELEMENT(KeyImport)
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeochatAdd3PIdJob::NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const std::optional<QJsonObject> &auth)
|
||||
NeochatAdd3PIdJob::NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Omittable<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("Add3PIDJob"), makePath("/_matrix/client/v3", "/account/3pid/add"))
|
||||
{
|
||||
QJsonObject _dataJson;
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
class NeochatAdd3PIdJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const std::optional<QJsonObject> &auth = {});
|
||||
explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Quotient::Omittable<QJsonObject> &auth = {});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const std::optional<QJsonObject> &auth)
|
||||
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password")
|
||||
{
|
||||
QJsonObject _data;
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
class NeochatChangePasswordJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const std::optional<QJsonObject> &auth = {});
|
||||
explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Quotient::Omittable<QJsonObject> &auth = {});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth)
|
||||
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const Omittable<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Post, QStringLiteral("DisableDeviceJob"), "_matrix/client/v3/account/deactivate")
|
||||
{
|
||||
QJsonObject data;
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth = {});
|
||||
explicit NeoChatDeactivateAccountJob(const Quotient::Omittable<QJsonObject> &auth = {});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const std::optional<QJsonObject> &auth)
|
||||
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
|
||||
: BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId).toLatin1())
|
||||
{
|
||||
QJsonObject _data;
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
class NeochatDeleteDeviceJob : public Quotient::BaseJob
|
||||
{
|
||||
public:
|
||||
explicit NeochatDeleteDeviceJob(const QString &deviceId, const std::optional<QJsonObject> &auth = {});
|
||||
explicit NeochatDeleteDeviceJob(const QString &deviceId, const Quotient::Omittable<QJsonObject> &auth = {});
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
|
||||
// TODO: Upstream to libQuotient
|
||||
class NeochatGetCommonRoomsJob : public Quotient::BaseJob
|
||||
|
||||
@@ -140,7 +140,7 @@ int main(int argc, char *argv[])
|
||||
KAboutData about(QStringLiteral("neochat"),
|
||||
i18n("NeoChat"),
|
||||
QStringLiteral(NEOCHAT_VERSION_STRING),
|
||||
i18n("Chat on Matrix"),
|
||||
i18n("Matrix client"),
|
||||
KAboutLicense::GPL_V3,
|
||||
i18n("© 2018-2020 Black Hat, 2020-2024 KDE Community"));
|
||||
about.addAuthor(i18n("Carl Schwan"),
|
||||
|
||||
@@ -163,7 +163,11 @@ void AccountEmoticonModel::setEmoticonImage(int index, const QUrl &source)
|
||||
QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl source)
|
||||
{
|
||||
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
co_await qCoro(job.get(), &BaseJob::finished);
|
||||
#else
|
||||
co_await qCoro(job, &BaseJob::finished);
|
||||
#endif
|
||||
if (job->error() != BaseJob::NoError) {
|
||||
co_return;
|
||||
}
|
||||
@@ -185,7 +189,11 @@ QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl sourc
|
||||
QCoro::Task<void> AccountEmoticonModel::doAddEmoticon(QUrl source, QString shortcode, QString description, QString type)
|
||||
{
|
||||
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
co_await qCoro(job.get(), &BaseJob::finished);
|
||||
#else
|
||||
co_await qCoro(job, &BaseJob::finished);
|
||||
#endif
|
||||
if (job->error() != BaseJob::NoError) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "enums/messagetype.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
@@ -18,7 +17,6 @@
|
||||
|
||||
using Action = ActionsModel::Action;
|
||||
using namespace Quotient;
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls, "#ffd500"_ls, "#ffff00"_ls, "#d4ff00"_ls, "#aaff00"_ls, "#80ff00"_ls,
|
||||
"#55ff00"_ls, "#2bff00"_ls, "#00ff00"_ls, "#00ff2b"_ls, "#00ff55"_ls, "#00ff80"_ls, "#00ffaa"_ls, "#00ffd5"_ls, "#00ffff"_ls,
|
||||
@@ -576,73 +574,3 @@ QList<Action> &ActionsModel::allActions() const
|
||||
{
|
||||
return actions;
|
||||
}
|
||||
|
||||
bool ActionsModel::handleQuickEditAction(NeoChatRoom *room, const QString &messageText)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
|
||||
auto match = sed.match(messageText);
|
||||
if (match.hasMatch()) {
|
||||
const QString regex = match.captured(1);
|
||||
const QString replacement = match.captured(2).toHtmlEscaped();
|
||||
const QString flags = match.captured(3);
|
||||
|
||||
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
|
||||
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
|
||||
if (event->senderId() == room->localMember().id() && event->has<EventContent::TextContent>()) {
|
||||
QString originalString;
|
||||
if (event->content()) {
|
||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content().get())->body;
|
||||
} else {
|
||||
originalString = event->plainBody();
|
||||
}
|
||||
if (flags == "/g"_L1) {
|
||||
room->postHtmlMessage(messageText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
|
||||
} else {
|
||||
room->postHtmlMessage(messageText,
|
||||
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
||||
event->msgtype(),
|
||||
{},
|
||||
event->id());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<std::optional<QString>, std::optional<Quotient::RoomMessageEvent::MsgType>> ActionsModel::handleAction(NeoChatRoom *room, ChatBarCache *chatBarCache)
|
||||
{
|
||||
auto sendText = chatBarCache->sendText();
|
||||
const auto edited = handleQuickEditAction(room, sendText);
|
||||
if (edited) {
|
||||
return std::make_pair(std::nullopt, std::nullopt);
|
||||
}
|
||||
|
||||
std::optional<Quotient::RoomMessageEvent::MsgType> messageType = std::nullopt;
|
||||
if (sendText.startsWith(QLatin1Char('/'))) {
|
||||
for (const auto &action : ActionsModel::instance().allActions()) {
|
||||
if (sendText.indexOf(action.prefix) == 1
|
||||
&& (sendText.indexOf(" "_ls) == action.prefix.length() + 1 || sendText.length() == action.prefix.length() + 1)) {
|
||||
sendText = action.handle(sendText.mid(action.prefix.length() + 1).trimmed(), room, chatBarCache);
|
||||
if (action.messageType.has_value()) {
|
||||
messageType = action.messageType;
|
||||
}
|
||||
if (action.messageAction) {
|
||||
break;
|
||||
} else {
|
||||
return std::make_pair(std::nullopt, std::nullopt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(sendText, messageType);
|
||||
}
|
||||
|
||||
@@ -90,21 +90,6 @@ public:
|
||||
*/
|
||||
QList<Action> &allActions() const;
|
||||
|
||||
/**
|
||||
* @brief Handle special sed style edit action.
|
||||
*
|
||||
* @return True if the message has a sed edit which was actioned. False otherwise.
|
||||
*/
|
||||
static bool handleQuickEditAction(NeoChatRoom *room, const QString &messageText);
|
||||
|
||||
/**
|
||||
* @brief Handle any action within the message contained in the given ChatBarCache.
|
||||
*
|
||||
* @return A modified or unmodified string that needs to be sent or an empty string if
|
||||
* the handled action replaces sending a normal message.
|
||||
*/
|
||||
static std::pair<std::optional<QString>, std::optional<Quotient::RoomMessageEvent::MsgType>> handleAction(NeoChatRoom *room, ChatBarCache *chatBarCache);
|
||||
|
||||
private:
|
||||
ActionsModel() = default;
|
||||
};
|
||||
|
||||
@@ -129,7 +129,11 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, m_connection->user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
auto innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connect(innerJob.get(), &BaseJob::success, this, onSuccess);
|
||||
#else
|
||||
connect(innerJob, &BaseJob::success, this, onSuccess);
|
||||
#endif
|
||||
} else {
|
||||
onSuccess();
|
||||
}
|
||||
|
||||
@@ -522,10 +522,19 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
||||
auto fileTransferInfo = m_room->cachedFileTransferInfo(event);
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->has<EventContent::FileContent>());
|
||||
const QMimeType mimeType = roomMessageEvent->get<EventContent::FileContent>()->mimeType;
|
||||
#else
|
||||
Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->hasFileContent());
|
||||
const QMimeType mimeType = roomMessageEvent->content()->fileInfo()->mimeType;
|
||||
#endif
|
||||
if (mimeType.name() == QStringLiteral("text/plain") || mimeType.parentMimeTypes().contains(QStringLiteral("text/plain"))) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString originalName = roomMessageEvent->get<EventContent::FileContent>()->originalName;
|
||||
#else
|
||||
QString originalName = roomMessageEvent->content()->fileInfo()->originalName;
|
||||
#endif
|
||||
if (originalName.isEmpty()) {
|
||||
originalName = roomMessageEvent->plainBody();
|
||||
}
|
||||
@@ -635,7 +644,7 @@ QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageCompon
|
||||
|
||||
void MessageContentModel::closeLinkPreview(int row)
|
||||
{
|
||||
if (row < 0 || row >= m_components.size()) {
|
||||
if (row < 0 || row > m_components.size()) {
|
||||
qWarning() << "closeLinkPreview() called with row" << row << "which does not exist. m_components.size() =" << m_components.size();
|
||||
return;
|
||||
}
|
||||
@@ -645,7 +654,6 @@ void MessageContentModel::closeLinkPreview(int row)
|
||||
m_removedLinkPreviews += m_components[row].attributes["link"_ls].toUrl();
|
||||
m_components.remove(row);
|
||||
m_components.squeeze();
|
||||
endResetModel();
|
||||
resetContent();
|
||||
}
|
||||
}
|
||||
@@ -658,7 +666,11 @@ void MessageContentModel::updateItineraryModel()
|
||||
}
|
||||
|
||||
if (auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (roomMessageEvent->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (roomMessageEvent->hasFileContent()) {
|
||||
#endif
|
||||
auto filePath = m_room->cachedFileTransferInfo(event).localPath;
|
||||
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
|
||||
delete m_itineraryModel;
|
||||
|
||||
@@ -505,7 +505,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (role == ProgressInfoRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (e->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (e->hasFileContent()) {
|
||||
#endif
|
||||
return QVariant::fromValue(m_currentRoom->cachedFileTransferInfo(&evt));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +311,12 @@ void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
|
||||
pushConditions.append(keywordCondition);
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto job = m_connection->callApi<Quotient::SetPushRuleJob>(PushRuleKind::kindString(kind),
|
||||
#else
|
||||
auto job = m_connection->callApi<Quotient::SetPushRuleJob>(QLatin1String("global"),
|
||||
PushRuleKind::kindString(kind),
|
||||
#endif
|
||||
keyword,
|
||||
actions,
|
||||
QString(),
|
||||
@@ -336,7 +341,11 @@ void PushRuleModel::removeKeyword(const QString &keyword)
|
||||
}
|
||||
|
||||
auto kind = PushRuleKind::kindString(m_rules[index].kind);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto job = m_connection->callApi<Quotient::DeletePushRuleJob>(kind, m_rules[index].id);
|
||||
#else
|
||||
auto job = m_connection->callApi<Quotient::DeletePushRuleJob>(QStringLiteral("global"), kind, m_rules[index].id);
|
||||
#endif
|
||||
connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() {
|
||||
qWarning() << QLatin1String("Unable to remove push rule for keyword %1: ").arg(m_rules[index].id) << job->errorString();
|
||||
});
|
||||
@@ -344,10 +353,18 @@ void PushRuleModel::removeKeyword(const QString &keyword)
|
||||
|
||||
void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled)
|
||||
{
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto job = m_connection->callApi<Quotient::IsPushRuleEnabledJob>(kind, ruleId);
|
||||
#else
|
||||
auto job = m_connection->callApi<Quotient::IsPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId);
|
||||
#endif
|
||||
connect(job, &Quotient::BaseJob::success, this, [job, kind, ruleId, enabled, this]() {
|
||||
if (job->enabled() != enabled) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
m_connection->callApi<Quotient::SetPushRuleEnabledJob>(kind, ruleId, enabled);
|
||||
#else
|
||||
m_connection->callApi<Quotient::SetPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId, enabled);
|
||||
#endif
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -361,7 +378,11 @@ void PushRuleModel::setNotificationRuleActions(const QString &kind, const QStrin
|
||||
actions = actionToVariant(action);
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
m_connection->callApi<Quotient::SetPushRuleActionsJob>(kind, ruleId, actions);
|
||||
#else
|
||||
m_connection->callApi<Quotient::SetPushRuleActionsJob>(QStringLiteral("global"), kind, ruleId, actions);
|
||||
#endif
|
||||
}
|
||||
|
||||
PushRuleAction::Action PushRuleModel::variantToAction(const QList<QVariant> &actions, bool enabled)
|
||||
|
||||
@@ -286,7 +286,6 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
|
||||
roles[IconRole] = "icon";
|
||||
roles[AttentionRole] = "attention";
|
||||
roles[FavouriteRole] = "favourite";
|
||||
roles[RoomTypeRole] = "roomType";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -386,11 +385,6 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
||||
if (role == FavouriteRole) {
|
||||
return room->isFavourite();
|
||||
}
|
||||
if (role == RoomTypeRole) {
|
||||
if (room->creation()) {
|
||||
return room->creation()->contentPart<QString>("type"_L1);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ public:
|
||||
IconRole,
|
||||
AttentionRole, /**< Whether there are any notifications. */
|
||||
FavouriteRole, /**< Whether the room is favourited. */
|
||||
RoomTypeRole, /**< The room's type. */
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
explicit RoomTreeModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -157,11 +157,6 @@ bool SortFilterRoomTreeModel::filterAcceptsRow(int source_row, const QModelIndex
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide rooms with defined types, assuming that data-holding rooms have a defined type
|
||||
if (!sourceModel()->data(index, RoomTreeModel::RoomTypeRole).toString().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static auto config = NeoChatConfig::self();
|
||||
if (config->allRoomsInHome() && RoomManager::instance().currentSpace().isEmpty()) {
|
||||
return acceptRoom;
|
||||
|
||||
@@ -93,7 +93,11 @@ void SpaceChildrenModel::refreshModel()
|
||||
});
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR >= 9
|
||||
void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::SpaceHierarchyRoomsChunk> children, const QModelIndex &parent)
|
||||
#else
|
||||
void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::ChildRoomsChunk> children, const QModelIndex &parent)
|
||||
#endif
|
||||
{
|
||||
SpaceTreeItem *parentItem = getItem(parent);
|
||||
|
||||
|
||||
@@ -144,5 +144,9 @@ private:
|
||||
|
||||
void refreshModel();
|
||||
|
||||
#if Quotient_VERSION_MINOR >= 9
|
||||
void insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::SpaceHierarchyRoomsChunk> children, const QModelIndex &parent = QModelIndex());
|
||||
#else
|
||||
void insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::ChildRoomsChunk> children, const QModelIndex &parent = QModelIndex());
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
#include <memory>
|
||||
|
||||
#include "chatbarcache.h"
|
||||
|
||||
@@ -87,7 +87,11 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
||||
return memberId;
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return m_currentRoom->member(memberId).avatarUrl();
|
||||
#else
|
||||
return m_currentRoom->memberAvatar(memberId).url();
|
||||
#endif
|
||||
}
|
||||
if (role == ObjectRole) {
|
||||
return QVariant::fromValue(memberId);
|
||||
@@ -172,7 +176,11 @@ void UserListModel::refreshAllMembers()
|
||||
|
||||
if (m_currentRoom != nullptr) {
|
||||
m_members = m_currentRoom->joinedMemberIds();
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
MemberSorter sorter;
|
||||
#else
|
||||
MemberSorter sorter(m_currentRoom);
|
||||
#endif
|
||||
std::sort(m_members.begin(), m_members.end(), [&sorter, this](const auto &left, const auto &right) {
|
||||
const auto leftPl = m_currentRoom->getUserPowerLevel(left);
|
||||
const auto rightPl = m_currentRoom->getUserPowerLevel(right);
|
||||
|
||||
@@ -261,7 +261,7 @@ Action=Popup
|
||||
Name=Share
|
||||
Name[ar]=شارك
|
||||
Name[ca]=Compartició
|
||||
Name[ca@valencia]=Compartiu
|
||||
Name[ca@valencia]=Compartició
|
||||
Name[cs]=Sdílet
|
||||
Name[de]=Teilen
|
||||
Name[el]=Κοινοποίηση
|
||||
|
||||
@@ -27,18 +27,6 @@
|
||||
<label>Use s/text/replacement syntax to edit your last message.</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="SendMessageWith" type="Enum">
|
||||
<label>Key combination to send a message</label>
|
||||
<choices>
|
||||
<choice name="Enter">
|
||||
<label>Enter</label>
|
||||
</choice>
|
||||
<choice name="CtrlEnter">
|
||||
<label>Ctrl+Enter</label>
|
||||
</choice>
|
||||
<default>Enter</default>
|
||||
</choices>
|
||||
</entry>
|
||||
<entry name="ShowLocalMessagesOnRight" type="bool">
|
||||
<label>"Show your messages on the right</label>
|
||||
<default>true</default>
|
||||
|
||||
@@ -533,6 +533,7 @@ LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
|
||||
return previewer;
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
KeyImport::Error NeoChatConnection::exportMegolmSessions(const QString &passphrase, const QString &path)
|
||||
{
|
||||
KeyImport keyImport;
|
||||
@@ -547,5 +548,6 @@ KeyImport::Error NeoChatConnection::exportMegolmSessions(const QString &passphra
|
||||
file.close();
|
||||
return KeyImport::Success;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "moc_neochatconnection.cpp"
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
#include <QCoroTask>
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
#include <Quotient/keyimport.h>
|
||||
#endif
|
||||
|
||||
#include "enums/messagetype.h"
|
||||
#include "linkpreviewer.h"
|
||||
@@ -158,7 +160,9 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE QString accountDataJsonString(const QString &type) const;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
Q_INVOKABLE Quotient::KeyImport::Error exportMegolmSessions(const QString &passphrase, const QString &path);
|
||||
#endif
|
||||
|
||||
qsizetype directChatNotifications() const;
|
||||
bool directChatsHaveHighlightNotifications() const;
|
||||
|
||||
@@ -74,8 +74,13 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
const auto m_event = evtIt->viewAs<RoomEvent>();
|
||||
QString mxcUrl;
|
||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event->has<EventContent::FileContentBase>()) {
|
||||
mxcUrl = event->get<EventContent::FileContentBase>()->url().toString();
|
||||
#else
|
||||
if (event->hasFileContent()) {
|
||||
mxcUrl = event->content()->fileInfo()->url().toString();
|
||||
#endif
|
||||
}
|
||||
} else if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
|
||||
mxcUrl = event->image().url().toString();
|
||||
@@ -216,7 +221,11 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
|
||||
auto mime = QMimeDatabase().mimeTypeForUrl(url);
|
||||
url.setScheme("file"_ls);
|
||||
QFileInfo fileInfo(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
EventContent::FileContentBase *content;
|
||||
#else
|
||||
EventContent::TypedBase *content;
|
||||
#endif
|
||||
if (mime.name().startsWith("image/"_ls)) {
|
||||
QImage image(url.toLocalFile());
|
||||
content = new EventContent::ImageContent(url, fileInfo.size(), mime, image.size(), fileInfo.fileName());
|
||||
@@ -231,7 +240,11 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
|
||||
} else {
|
||||
content = new EventContent::FileContent(url, fileInfo.size(), mime, fileInfo.fileName());
|
||||
}
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, std::unique_ptr<EventContent::FileContentBase>(content));
|
||||
#else
|
||||
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, content);
|
||||
#endif
|
||||
setHasFileUploading(true);
|
||||
connect(this, &Room::fileTransferCompleted, [this, txnId](const QString &id, FileSourceInfo) {
|
||||
if (id == txnId) {
|
||||
@@ -363,8 +376,13 @@ bool NeoChatRoom::lastEventIsSpoiler() const
|
||||
{
|
||||
if (auto event = lastEvent()) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (e->has<EventContent::TextContent>() && e->content() && e->mimeType().name() == "text/html"_ls) {
|
||||
auto htmlBody = e->get<EventContent::TextContent>()->body;
|
||||
#else
|
||||
if (e->hasTextContent() && e->content() && e->mimeType().name() == "text/html"_ls) {
|
||||
auto htmlBody = static_cast<const Quotient::EventContent::TextContent *>(e->content())->body;
|
||||
#endif
|
||||
return htmlBody.contains("data-mx-spoiler"_ls);
|
||||
}
|
||||
}
|
||||
@@ -896,7 +914,11 @@ QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QStri
|
||||
}
|
||||
for (const auto &e : events) {
|
||||
auto job = connection()->callApi<RedactEventJob>(id(), QString::fromLatin1(QUrl::toPercentEncoding(e)), connection()->generateTxnId(), reason);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
co_await qCoro(job.get(), &BaseJob::finished);
|
||||
#else
|
||||
co_await qCoro(job, &BaseJob::finished);
|
||||
#endif
|
||||
if (job->error() != BaseJob::Success) {
|
||||
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
|
||||
break;
|
||||
@@ -1195,7 +1217,11 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
for (const auto &i : roomRuleArray) {
|
||||
QJsonObject roomRule = i.toObject();
|
||||
if (roomRule["rule_id"_ls] == id()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection()->callApi<DeletePushRuleJob>("room"_ls, id());
|
||||
#else
|
||||
connection()->callApi<DeletePushRuleJob>(QLatin1String("global"), "room"_ls, id());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1206,7 +1232,11 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
for (const auto &i : overrideRuleArray) {
|
||||
QJsonObject overrideRule = i.toObject();
|
||||
if (overrideRule["rule_id"_ls] == id()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection()->callApi<DeletePushRuleJob>("override"_ls, id());
|
||||
#else
|
||||
connection()->callApi<DeletePushRuleJob>("global"_ls, "override"_ls, id());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1242,9 +1272,17 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions = {pushCondition};
|
||||
|
||||
// Add new override rule and make sure it's enabled
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto job = connection()->callApi<SetPushRuleJob>("override"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#else
|
||||
auto job = connection()->callApi<SetPushRuleJob>("global"_ls, "override"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#endif
|
||||
connect(job, &BaseJob::success, this, [this]() {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("override"_ls, id(), true);
|
||||
#else
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("global"_ls, "override"_ls, id(), true);
|
||||
#endif
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1268,9 +1306,17 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
// No conditions for a room rule
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#else
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#endif
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("room"_ls, id(), true);
|
||||
#else
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
#endif
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1299,9 +1345,17 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
// Add new room rule and make sure enabled
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#else
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
#endif
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("room"_ls, id(), true);
|
||||
#else
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
#endif
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1392,7 +1446,11 @@ void NeoChatRoom::openEventMediaExternally(const QString &eventId)
|
||||
const auto evtIt = findInTimeline(eventId);
|
||||
if (evtIt != messageEvents().rend() && is<RoomMessageEvent>(**evtIt)) {
|
||||
const auto event = evtIt->viewAs<RoomMessageEvent>();
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (event->hasFileContent()) {
|
||||
#endif
|
||||
const auto transferInfo = cachedFileTransferInfo(event);
|
||||
if (transferInfo.completed()) {
|
||||
UrlHelper helper;
|
||||
@@ -1425,7 +1483,11 @@ void NeoChatRoom::copyEventMedia(const QString &eventId)
|
||||
const auto evtIt = findInTimeline(eventId);
|
||||
if (evtIt != messageEvents().rend() && is<RoomMessageEvent>(**evtIt)) {
|
||||
const auto event = evtIt->viewAs<RoomMessageEvent>();
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (event->has<EventContent::FileContent>()) {
|
||||
#else
|
||||
if (event->hasFileContent()) {
|
||||
#endif
|
||||
const auto transferInfo = fileTransferInfo(eventId);
|
||||
if (transferInfo.completed()) {
|
||||
Clipboard clipboard;
|
||||
@@ -1458,8 +1520,13 @@ FileTransferInfo NeoChatRoom::cachedFileTransferInfo(const Quotient::RoomEvent *
|
||||
QString mxcUrl;
|
||||
int total = 0;
|
||||
if (auto evt = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
if (evt->has<EventContent::FileContent>()) {
|
||||
const auto fileContent = evt->get<EventContent::FileContent>();
|
||||
#else
|
||||
if (evt->hasFileContent()) {
|
||||
const auto fileContent = evt->content()->fileInfo();
|
||||
#endif
|
||||
|
||||
mxcUrl = fileContent->url().toString();
|
||||
total = fileContent->payloadSize;
|
||||
@@ -1817,4 +1884,13 @@ void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, con
|
||||
setState(type, stateKey, QJsonDocument::fromJson(content).object());
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR == 8
|
||||
QList<RoomMember> NeoChatRoom::otherMembersTyping() const
|
||||
{
|
||||
auto memberTyping = membersTyping();
|
||||
memberTyping.removeAll(localMember());
|
||||
return memberTyping;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "moc_neochatroom.cpp"
|
||||
|
||||
@@ -208,6 +208,10 @@ class NeoChatRoom : public Quotient::Room
|
||||
*/
|
||||
Q_PROPERTY(ChatBarCache *threadCache READ threadCache CONSTANT)
|
||||
|
||||
#if Quotient_VERSION_MINOR == 8
|
||||
Q_PROPERTY(QList<Quotient::RoomMember> otherMembersTyping READ otherMembersTyping NOTIFY typingChanged)
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
|
||||
|
||||
@@ -619,6 +623,10 @@ private:
|
||||
void cleanupExtraEventRange(Quotient::RoomEventsRange events);
|
||||
void cleanupExtraEvent(const QString &eventId);
|
||||
|
||||
#if Quotient_VERSION_MINOR == 8
|
||||
QList<Quotient::RoomMember> otherMembersTyping() const;
|
||||
#endif
|
||||
|
||||
private Q_SLOTS:
|
||||
void updatePushNotificationState(QString type);
|
||||
|
||||
|
||||
@@ -73,7 +73,11 @@ QString NeochatRoomMember::displayName() const
|
||||
}
|
||||
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.displayName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.displayName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::htmlSafeDisplayName() const
|
||||
@@ -83,7 +87,11 @@ QString NeochatRoomMember::htmlSafeDisplayName() const
|
||||
}
|
||||
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.htmlSafeDisplayName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.htmlSafeDisplayName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::fullName() const
|
||||
@@ -93,7 +101,11 @@ QString NeochatRoomMember::fullName() const
|
||||
}
|
||||
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.fullName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.fullName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::htmlSafeFullName() const
|
||||
@@ -103,7 +115,11 @@ QString NeochatRoomMember::htmlSafeFullName() const
|
||||
}
|
||||
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.htmlSafeFullName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.htmlSafeFullName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::disambiguatedName() const
|
||||
@@ -113,7 +129,11 @@ QString NeochatRoomMember::disambiguatedName() const
|
||||
}
|
||||
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.disambiguatedName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.disambiguatedName();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::htmlSafeDisambiguatedName() const
|
||||
@@ -123,7 +143,11 @@ QString NeochatRoomMember::htmlSafeDisambiguatedName() const
|
||||
}
|
||||
|
||||
const auto memberObject = m_room->member(m_memberId);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return memberObject.isEmpty() ? id() : memberObject.htmlSafeDisambiguatedName();
|
||||
#else
|
||||
return memberObject.id().isEmpty() ? id() : memberObject.htmlSafeDisambiguatedName();
|
||||
#endif
|
||||
}
|
||||
|
||||
int NeochatRoomMember::hue() const
|
||||
|
||||
@@ -148,7 +148,11 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
|
||||
|
||||
QImage avatar_image;
|
||||
if (!sender.avatarUrl().isEmpty()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
avatar_image = room->member(sender.id()).avatar(128, 128, {});
|
||||
#else
|
||||
avatar_image = room->memberAvatar(sender.id()).get(connection, 128, {});
|
||||
#endif
|
||||
} else {
|
||||
avatar_image = room->avatar(128);
|
||||
}
|
||||
@@ -294,7 +298,11 @@ void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
|
||||
|
||||
QImage avatar_image;
|
||||
if (roomMemberEvent && !room->member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
avatar_image = room->member(roomMemberEvent->senderId()).avatar(128, 128, {});
|
||||
#else
|
||||
avatar_image = room->memberAvatar(roomMemberEvent->senderId()).get(room->connection(), 128, [] {});
|
||||
#endif
|
||||
} else {
|
||||
qWarning() << "using this room's avatar";
|
||||
avatar_image = room->avatar(128);
|
||||
|
||||
@@ -30,7 +30,6 @@ class PollHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("Use NeoChatRoom::poll")
|
||||
|
||||
/**
|
||||
* @brief The question for the poll.
|
||||
@@ -92,7 +91,7 @@ Q_SIGNALS:
|
||||
void hasEndedChanged();
|
||||
|
||||
private:
|
||||
const Quotient::PollStartEvent *m_pollStartEvent = nullptr;
|
||||
const Quotient::PollStartEvent *m_pollStartEvent;
|
||||
|
||||
void updatePoll(Quotient::RoomEventsRange events);
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"Name[nn]": "Tobias Fella",
|
||||
"Name[pl]": "Tobias Fella",
|
||||
"Name[ru]": "Tobias Fella",
|
||||
"Name[sk]": "Tobias Fella",
|
||||
"Name[sl]": "Tobias Fella",
|
||||
"Name[sv]": "Tobias Fella",
|
||||
"Name[ta]": "டோபியாஸ் ஃபெல்லா",
|
||||
@@ -94,7 +93,6 @@
|
||||
"Name[nn]": "NeoChat",
|
||||
"Name[pl]": "NeoChat",
|
||||
"Name[ru]": "NeoChat",
|
||||
"Name[sk]": "NeoChat",
|
||||
"Name[sl]": "NeoChat",
|
||||
"Name[sv]": "NeoChat",
|
||||
"Name[ta]": "நியோச்சாட்",
|
||||
|
||||
@@ -87,21 +87,13 @@ SearchPage {
|
||||
}
|
||||
|
||||
listHeaderDelegate: Delegates.RoundedItemDelegate {
|
||||
id: delegate
|
||||
|
||||
onClicked: _private.openManualRoomDialog()
|
||||
|
||||
activeFocusOnTab: false // We handle moving to this item via up/down arrows, otherwise the tab order is wacky
|
||||
text: i18n("Enter a Room Manually")
|
||||
text: i18n("Enter a room address")
|
||||
icon.name: "compass"
|
||||
icon.width: Kirigami.Units.gridUnit * 2
|
||||
icon.height: Kirigami.Units.gridUnit * 2
|
||||
|
||||
contentItem: Kirigami.IconTitleSubtitle {
|
||||
icon: icon.fromControlsIcon(delegate.icon)
|
||||
title: delegate.text
|
||||
subtitle: i18n("If you already know a room's address or alias, and it isn't shown here.")
|
||||
}
|
||||
}
|
||||
|
||||
listFooterDelegate: QQC2.ProgressBar {
|
||||
|
||||
@@ -23,7 +23,7 @@ Kirigami.Dialog {
|
||||
*/
|
||||
signal roomSelected(string roomId, string displayName, url avatarUrl, string alias, string topic, int memberCount, bool isJoined)
|
||||
|
||||
title: i18nc("@title", "Manually Enter a Room")
|
||||
title: i18nc("@title", "Room ID or Alias")
|
||||
|
||||
width: Math.min(root.Window.window.width, Kirigami.Units.gridUnit * 24)
|
||||
leftPadding: 0
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user