Compare commits
1 Commits
work/tobia
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f060dcfe6 |
@@ -10,7 +10,6 @@ Dependencies:
|
||||
'frameworks/ki18n': '@latest-kf6'
|
||||
'frameworks/kconfig': '@latest-kf6'
|
||||
'frameworks/syntax-highlighting': '@latest-kf6'
|
||||
'frameworks/kiconthemes': '@latest-kf6'
|
||||
'frameworks/kitemmodels': '@latest-kf6'
|
||||
'frameworks/kquickcharts': '@latest-kf6'
|
||||
'frameworks/knotifications': '@latest-kf6'
|
||||
@@ -29,11 +28,15 @@ Dependencies:
|
||||
'frameworks/kio': '@latest-kf6'
|
||||
'frameworks/kwindowsystem': '@latest-kf6'
|
||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||
|
||||
- 'on': ['Linux', 'FreeBSD', 'Android']
|
||||
'require':
|
||||
'libraries/kunifiedpush': '@latest-kf6'
|
||||
|
||||
- 'on': ['Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@latest-kf6'
|
||||
'frameworks/purpose': '@latest-kf6'
|
||||
'libraries/kunifiedpush': '@latest-kf6'
|
||||
|
||||
- 'on': ['Linux']
|
||||
'require':
|
||||
@@ -43,4 +46,3 @@ Options:
|
||||
per-test-timeout: 90
|
||||
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows']
|
||||
run-qmllint: True
|
||||
enable-lsan: True
|
||||
|
||||
@@ -65,7 +65,7 @@ if (QT_KNOWN_POLICY_QTP0004)
|
||||
qt_policy(SET QTP0004 NEW)
|
||||
endif ()
|
||||
|
||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme IconThemes)
|
||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
|
||||
set_package_properties(KF6 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
@@ -148,7 +148,7 @@ set_package_properties(KF6DocTools PROPERTIES DESCRIPTION
|
||||
|
||||
option(WITH_UNIFIEDPUSH "Build with KUnifiedPush support" ON)
|
||||
|
||||
if (ANDROID OR APPLE OR WIN32 OR HAIKU)
|
||||
if (APPLE OR WIN32 OR HAIKU)
|
||||
set(WITH_UNIFIEDPUSH OFF)
|
||||
endif()
|
||||
|
||||
@@ -178,7 +178,7 @@ add_definitions(-DQT_NO_FOREACH)
|
||||
add_subdirectory(src)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test HttpServer QuickTest)
|
||||
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test HttpServer)
|
||||
add_subdirectory(autotests)
|
||||
# add_subdirectory(appiumtests)
|
||||
if (NOT ANDROID)
|
||||
|
||||
19
README.md
19
README.md
@@ -25,10 +25,15 @@ Qt-based SDK for the [Matrix Protocol](https://spec.matrix.org/).
|
||||
|
||||
## Features
|
||||
|
||||
NeoChat aims to be a fully featured application for the Matrix specification. As such, most parts of the current specification are supported, with the notable exceptions
|
||||
of VoIP, threads, and some aspects of End-to-End Encryption. There are a few other smaller omissions due to the Matrix spec constantly
|
||||
NeoChat aims to be a fully featured application for the Matrix specification. As such most parts of the current specification are supported, with the notable exceptions
|
||||
of VoIP, threads, and some aspects of End-to-End Encryption. There are a few other smaller omissions due to the fact that the Matrix spec is constantly
|
||||
evolving, but the aim remains to provide eventual support for the entire spec.
|
||||
|
||||
Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:
|
||||
- Polls - MSC3381
|
||||
- Sticker Packs - MSC2545
|
||||
- Location Events - MSC3488
|
||||
|
||||
## Get it
|
||||
|
||||
Details where to find stable releases for NeoChat can be found on its [homepage](https://apps.kde.org/neochat).
|
||||
@@ -43,12 +48,12 @@ The best way to build KDE apps during development is to use `kdesrc-build`. The
|
||||
the KDE community website's get involved section under [development](https://community.kde.org/Get_Involved/development). This
|
||||
is primarily aimed at Linux development.
|
||||
|
||||
For Windows and Android, [Craft](https://invent.kde.org/packaging/craft) is the primary choice. There are guides for setting up
|
||||
For Windows and Android [Craft](https://invent.kde.org/packaging/craft) is the primary choice. There are guides for setting up
|
||||
development environments for [Windows](https://community.kde.org/Get_Involved/development/Windows) and [Android](https://develop.kde.org/docs/packaging/android/building_applications/).
|
||||
|
||||
## Running
|
||||
|
||||
Start the executable in your preferred way – either from the build directory or from the installed location.
|
||||
Just start the executable in your preferred way - either from the build directory or from the installed location.
|
||||
|
||||
## Tests
|
||||
|
||||
@@ -61,12 +66,12 @@ be complete.
|
||||
|
||||

|
||||
|
||||
Currently, the number of tests is limited but growing. If anyone wants to help improve this, those
|
||||
Currently the number of tests is limited, but growing. If anyone wants to help improve this, those
|
||||
contributions would be especially welcome.
|
||||
|
||||
## Contributing
|
||||
|
||||
As is the case throughout the KDE ecosystem, contributions are welcome from all. The code base is managed in the
|
||||
As is the case throughout the KDE ecosystem contributions are welcome from all. The code base is managed in the
|
||||
[NeoChat repository](https://invent.kde.org/network/neochat) of the KDE Gitlab instance.
|
||||
|
||||
- [Code of Conduct](https://kde.org/code-of-conduct)
|
||||
@@ -81,7 +86,7 @@ The best place to reach the maintainers is on the KDE Matrix instance in the Neo
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
NeoChat uses [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
|
||||
NeoChat utilizes [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
|
||||
|
||||
NeoChat is a fork of [Spectral](https://gitlab.com/spectral-im/spectral/).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
|
||||
|
||||
ecm_add_test(
|
||||
neochatroomtest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test Qt::HttpServer neochat_server
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME neochatroomtest
|
||||
)
|
||||
|
||||
@@ -41,10 +41,16 @@ ecm_add_test(
|
||||
|
||||
ecm_add_test(
|
||||
chatbarcachetest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test Qt::HttpServer neochat_server
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME chatbarcachetest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
chatdocumenthandlertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME chatdocumenthandlertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
timelinemessagemodeltest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
@@ -98,45 +104,3 @@ ecm_add_test(
|
||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
||||
TEST_NAME roommanagertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
modeltest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test neochat_server Devtools
|
||||
TEST_NAME modeltest
|
||||
)
|
||||
|
||||
macro(add_qml_tests)
|
||||
if (WIN32)
|
||||
set(_extra_args -platform offscreen)
|
||||
endif()
|
||||
|
||||
foreach(test ${ARGV})
|
||||
add_test(NAME ${test}
|
||||
COMMAND qmltest
|
||||
${_extra_args}
|
||||
-input ${test}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
endforeach()
|
||||
endmacro()
|
||||
|
||||
add_executable(qmltest qmltest.cpp
|
||||
chatkeyhelpertesthelper.h
|
||||
chatmarkdownhelpertestwrapper.h
|
||||
chattextitemhelpertesthelper.h
|
||||
)
|
||||
qt_add_qml_module(qmltest URI NeoChatTestUtils)
|
||||
|
||||
target_link_libraries(qmltest
|
||||
PRIVATE
|
||||
Qt6::Qml
|
||||
Qt6::QuickTest
|
||||
LibNeoChat
|
||||
LibNeoChatplugin
|
||||
)
|
||||
|
||||
add_qml_tests(
|
||||
chattextitemhelpertest.qml
|
||||
chatmarkdownhelpertest.qml
|
||||
chatkeyhelpertest.qml
|
||||
)
|
||||
|
||||
@@ -63,7 +63,7 @@ void ActionsTest::testActions_data()
|
||||
QTest::addColumn<std::optional<QString>>("resultText");
|
||||
QTest::addColumn<std::optional<Quotient::RoomMessageEvent::MsgType>>("type");
|
||||
|
||||
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\\\_(ツ)\\_/¯ Hello"_s)
|
||||
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\_(ツ)_/¯ Hello"_s)
|
||||
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
||||
QTest::newRow("lenny") << u"/lenny Hello"_s << std::make_optional(u"( ͡° ͜ʖ ͡°) Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
|
||||
QTest::newRow("tableflip") << u"/tableflip Hello"_s << std::make_optional(u"(╯°□°)╯︵ ┻━┻ Hello"_s)
|
||||
@@ -88,7 +88,7 @@ void ActionsTest::testActions()
|
||||
QFETCH(std::optional<QString>, resultText);
|
||||
QFETCH(std::optional<Quotient::RoomMessageEvent::MsgType>, type);
|
||||
|
||||
auto cache = new ChatBarCache(this);
|
||||
auto cache = new ChatBarCache();
|
||||
cache->setText(command);
|
||||
auto result = ActionsModel::handleAction(room, cache);
|
||||
QCOMPARE(resultText, std::get<std::optional<QString>>(result));
|
||||
|
||||
@@ -11,13 +11,9 @@
|
||||
#include <Quotient/syncdata.h>
|
||||
#include <qtestcase.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "chatbarcache.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include "server.h"
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -28,9 +24,7 @@ class ChatBarCacheTest : public QObject
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
NeoChatRoom *room = nullptr;
|
||||
Server server;
|
||||
QString eventId;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
@@ -46,31 +40,8 @@ private Q_SLOTS:
|
||||
|
||||
void ChatBarCacheTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true, this);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
|
||||
const auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
eventId = server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
|
||||
server.joinUser(room->id(), u"@foo:server.com"_s);
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
|
||||
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, "test-min-sync.json"_L1);
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::empty()
|
||||
@@ -89,9 +60,8 @@ void ChatBarCacheTest::empty()
|
||||
|
||||
void ChatBarCacheTest::noRoom()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache());
|
||||
chatBarCache->setReplyId(eventId);
|
||||
chatBarCache->setReplyId(u"$153456789:example.org"_s);
|
||||
|
||||
// These should return empty even though a reply ID has been set because the
|
||||
// ChatBarCache has no parent.
|
||||
@@ -105,10 +75,9 @@ void ChatBarCacheTest::noRoom()
|
||||
|
||||
void ChatBarCacheTest::badParent()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
|
||||
QScopedPointer<QObject> badParent(new QObject());
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(badParent.get()));
|
||||
chatBarCache->setReplyId(eventId);
|
||||
chatBarCache->setReplyId(u"$153456789:example.org"_s);
|
||||
|
||||
// These should return empty even though a reply ID has been set because the
|
||||
// ChatBarCache has no parent.
|
||||
@@ -125,15 +94,15 @@ void ChatBarCacheTest::reply()
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
chatBarCache->setText(u"some text"_s);
|
||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
||||
chatBarCache->setReplyId(eventId);
|
||||
chatBarCache->setReplyId(u"$153456789:example.org"_s);
|
||||
|
||||
QCOMPARE(chatBarCache->text(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->isReplying(), true);
|
||||
QCOMPARE(chatBarCache->replyId(), eventId);
|
||||
QCOMPARE(chatBarCache->replyId(), u"$153456789:example.org"_s);
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
QCOMPARE(chatBarCache->editId(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@foo:server.com"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"foo"_s);
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
|
||||
}
|
||||
@@ -143,26 +112,22 @@ void ChatBarCacheTest::replyMissingUser()
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
chatBarCache->setText(u"some text"_s);
|
||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
||||
chatBarCache->setReplyId(eventId);
|
||||
chatBarCache->setReplyId(u"$153456789:example.org"_s);
|
||||
|
||||
QCOMPARE(chatBarCache->text(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->isReplying(), true);
|
||||
QCOMPARE(chatBarCache->replyId(), eventId);
|
||||
QCOMPARE(chatBarCache->replyId(), u"$153456789:example.org"_s);
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
QCOMPARE(chatBarCache->editId(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@foo:server.com"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"foo"_s);
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
|
||||
|
||||
QSignalSpy relationAuthorIsPresentSpy(chatBarCache.get(), &ChatBarCache::relationAuthorIsPresentChanged);
|
||||
|
||||
// sync again, which will simulate the reply user leaving the room
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
server.sendStateEvent(room->id(), u"m.room.member"_s, u"@foo:server.com"_s, {{u"membership"_s, u"leave"_s}});
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
room->syncNewEvents(u"test-min-sync-extra-sync.json"_s);
|
||||
|
||||
QTRY_COMPARE(relationAuthorIsPresentSpy.count(), 1);
|
||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), false);
|
||||
@@ -174,19 +139,19 @@ void ChatBarCacheTest::edit()
|
||||
|
||||
chatBarCache->setText(u"some text"_s);
|
||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
||||
connect(chatBarCache.get(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) {
|
||||
connect(chatBarCache.get(), &ChatBarCache::relationIdChanged, this, [](const QString &oldEventId, const QString &newEventId) {
|
||||
QCOMPARE(oldEventId, QString());
|
||||
QCOMPARE(newEventId, eventId);
|
||||
QCOMPARE(newEventId, QString(u"$153456789:example.org"_s));
|
||||
});
|
||||
chatBarCache->setEditId(eventId);
|
||||
chatBarCache->setEditId(u"$153456789:example.org"_s);
|
||||
|
||||
QCOMPARE(chatBarCache->text(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->isReplying(), false);
|
||||
QCOMPARE(chatBarCache->replyId(), QString());
|
||||
QCOMPARE(chatBarCache->isEditing(), true);
|
||||
QCOMPARE(chatBarCache->editId(), eventId);
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@foo:server.com"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"foo"_s);
|
||||
QCOMPARE(chatBarCache->editId(), u"$153456789:example.org"_s);
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
}
|
||||
|
||||
@@ -194,7 +159,7 @@ void ChatBarCacheTest::attachment()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
chatBarCache->setText(u"some text"_s);
|
||||
chatBarCache->setEditId(eventId);
|
||||
chatBarCache->setEditId(u"$153456789:example.org"_s);
|
||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
||||
|
||||
QCOMPARE(chatBarCache->text(), u"some text"_s);
|
||||
|
||||
36
autotests/chatdocumenthandlertest.cpp
Normal file
36
autotests/chatdocumenthandlertest.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
class ChatDocumentHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
ChatDocumentHandler emptyHandler;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void nullComplete();
|
||||
};
|
||||
|
||||
void ChatDocumentHandlerTest::initTestCase()
|
||||
{
|
||||
// HACK: this is to stop KStatusNotifierItem SEGFAULTING on cleanup.
|
||||
NeoChatConfig::self()->setSystemTray(false);
|
||||
}
|
||||
|
||||
void ChatDocumentHandlerTest::nullComplete()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "complete called with m_document set to nullptr.");
|
||||
emptyHandler.complete(0);
|
||||
}
|
||||
|
||||
QTEST_MAIN(ChatDocumentHandlerTest)
|
||||
#include "chatdocumenthandlertest.moc"
|
||||
@@ -1,88 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtTest
|
||||
|
||||
import org.kde.neochat.libneochat
|
||||
|
||||
import NeoChatTestUtils
|
||||
|
||||
TestCase {
|
||||
name: "ChatKeyHelperTest"
|
||||
|
||||
TextEdit {
|
||||
id: textEdit
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
event.accepted = testHelper.keyHelper.handleKey(event.key, event.modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
ChatTextItemHelper {
|
||||
id: textItemHelper
|
||||
|
||||
textItem: textEdit
|
||||
}
|
||||
|
||||
ChatKeyHelperTestHelper {
|
||||
id: testHelper
|
||||
|
||||
textItem: textItemHelper
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyUp
|
||||
target: testHelper.keyHelper
|
||||
signalName: "unhandledUp"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyDown
|
||||
target: testHelper.keyHelper
|
||||
signalName: "unhandledDown"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyDelete
|
||||
target: testHelper.keyHelper
|
||||
signalName: "unhandledDelete"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyBackSpace
|
||||
target: testHelper.keyHelper
|
||||
signalName: "unhandledBackspace"
|
||||
}
|
||||
|
||||
function init(): void {
|
||||
textEdit.clear();
|
||||
spyUp.clear();
|
||||
spyDown.clear();
|
||||
spyDelete.clear();
|
||||
spyBackSpace.clear();
|
||||
textEdit.forceActiveFocus();
|
||||
}
|
||||
|
||||
function cleanupTestCase(): void {
|
||||
testHelper.textItem = null;
|
||||
textItemHelper.textItem = null;
|
||||
}
|
||||
|
||||
function test_upDown(): void {
|
||||
textEdit.insert(0, "line 1\nline 2\nline 3")
|
||||
textEdit.cursorPosition = 0;
|
||||
keyClick(Qt.Key_Up);
|
||||
compare(spyUp.count, 1);
|
||||
compare(spyDown.count, 0);
|
||||
keyClick(Qt.Key_Down);
|
||||
compare(spyUp.count, 1);
|
||||
compare(spyDown.count, 0);
|
||||
keyClick(Qt.Key_Down);
|
||||
compare(spyUp.count, 1);
|
||||
compare(spyDown.count, 0);
|
||||
keyClick(Qt.Key_Down);
|
||||
compare(spyUp.count, 1);
|
||||
compare(spyDown.count, 1);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocumentFragment>
|
||||
|
||||
#include "chatkeyhelper.h"
|
||||
#include "chattextitemhelper.h"
|
||||
|
||||
class ChatKeyHelperTestHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(ChatTextItemHelper *textItem READ textItem WRITE setTextItem NOTIFY textItemChanged)
|
||||
|
||||
Q_PROPERTY(ChatKeyHelper *keyHelper READ keyHelper CONSTANT)
|
||||
|
||||
public:
|
||||
explicit ChatKeyHelperTestHelper(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
, m_keyHelper(new ChatKeyHelper(this))
|
||||
{
|
||||
}
|
||||
|
||||
ChatTextItemHelper *textItem() const
|
||||
{
|
||||
return m_keyHelper->textItem;
|
||||
}
|
||||
void setTextItem(ChatTextItemHelper *textItem)
|
||||
{
|
||||
if (textItem == m_keyHelper->textItem) {
|
||||
return;
|
||||
}
|
||||
m_keyHelper->textItem = textItem;
|
||||
Q_EMIT textItemChanged();
|
||||
}
|
||||
|
||||
ChatKeyHelper *keyHelper() const
|
||||
{
|
||||
return m_keyHelper;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void textItemChanged();
|
||||
|
||||
private:
|
||||
QPointer<ChatKeyHelper> m_keyHelper;
|
||||
};
|
||||
@@ -1,173 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtTest
|
||||
|
||||
import org.kde.neochat.libneochat
|
||||
|
||||
import NeoChatTestUtils
|
||||
|
||||
TestCase {
|
||||
name: "ChatMarkdownHelperTest"
|
||||
|
||||
TextEdit {
|
||||
id: textEdit
|
||||
|
||||
textFormat: TextEdit.RichText
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: textEdit2
|
||||
}
|
||||
|
||||
ChatMarkdownHelperTestWrapper {
|
||||
id: chatMarkdownHelper
|
||||
|
||||
textItem: textEdit
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyItem
|
||||
target: chatMarkdownHelper
|
||||
signalName: "textItemChanged"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyUnhandledFormat
|
||||
target: chatMarkdownHelper
|
||||
signalName: "unhandledBlockFormat"
|
||||
}
|
||||
|
||||
function initTestCase(): void {
|
||||
textEdit.forceActiveFocus();
|
||||
}
|
||||
|
||||
function cleanup(): void {
|
||||
chatMarkdownHelper.clear();
|
||||
compare(chatMarkdownHelper.checkText(""), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
compare(textEdit.cursorPosition, 0);
|
||||
}
|
||||
|
||||
function test_item(): void {
|
||||
spyItem.clear();
|
||||
compare(chatMarkdownHelper.textItem, textEdit);
|
||||
chatMarkdownHelper.textItem = textEdit2;
|
||||
compare(chatMarkdownHelper.textItem, textEdit2);
|
||||
chatMarkdownHelper.textItem = textEdit;
|
||||
compare(chatMarkdownHelper.textItem, textEdit);
|
||||
}
|
||||
|
||||
function test_textFormat_data() {
|
||||
return [
|
||||
{tag: "bold", input: "**b** ", outText: ["*", "**", "b", "b*", "b**", "b "], outFormats: [[], [], [RichFormat.Bold], [RichFormat.Bold], [RichFormat.Bold], []], unhandled: 0},
|
||||
{tag: "italic", input: "*i* ", outText: ["*", "i", "i*", "i "], outFormats: [[], [RichFormat.Italic], [RichFormat.Italic], []], unhandled: 0},
|
||||
{tag: "heading 1", input: "# h", outText: ["#", "# ", "h"], outFormats: [[], [], [RichFormat.Bold, RichFormat.Heading1]], unhandled: 0},
|
||||
{tag: "heading 2", input: "## h", outText: ["#", "##", "## ", "h"], outFormats: [[], [], [], [RichFormat.Bold, RichFormat.Heading2]], unhandled: 0},
|
||||
{tag: "heading 3", input: "### h", outText: ["#", "##", "###", "### ", "h"], outFormats: [[], [], [], [], [RichFormat.Bold, RichFormat.Heading3]], unhandled: 0},
|
||||
{tag: "heading 4", input: "#### h", outText: ["#", "##", "###", "####", "#### ", "h"], outFormats: [[], [], [], [], [], [RichFormat.Bold, RichFormat.Heading4]], unhandled: 0},
|
||||
{tag: "heading 5", input: "##### h", outText: ["#", "##", "###", "####", "#####", "##### ", "h"], outFormats: [[], [], [], [], [], [], [RichFormat.Bold, RichFormat.Heading5]], unhandled: 0},
|
||||
{tag: "heading 6", input: "###### h", outText: ["#", "##", "###", "####", "#####", "######", "###### ", "h"], outFormats: [[], [], [], [], [], [] ,[], [RichFormat.Bold, RichFormat.Heading6]], unhandled: 0},
|
||||
{tag: "quote", input: "> q", outText: [">", "> ", "q"], outFormats: [[], [], []], unhandled: 1},
|
||||
{tag: "quote - no space", input: ">q", outText: [">", "q"], outFormats: [[], [], []], unhandled: 1},
|
||||
{tag: "unorderedlist 1", input: "* l", outText: ["*", "* ", "l"], outFormats: [[], [], [RichFormat.UnorderedList]], unhandled: 0},
|
||||
{tag: "unorderedlist 2", input: "- l", outText: ["-", "- ", "l"], outFormats: [[], [], [RichFormat.UnorderedList]], unhandled: 0},
|
||||
{tag: "orderedlist 1", input: "1. l", outText: ["1", "1.", "1. ", "l"], outFormats: [[], [], [], [RichFormat.OrderedList]], unhandled: 0},
|
||||
{tag: "orderedlist 2", input: "1) l", outText: ["1", "1)", "1) ", "l"], outFormats: [[], [], [], [RichFormat.OrderedList]], unhandled: 0},
|
||||
{tag: "inline code", input: "`c` ", outText: ["`", "c", "c`", "c "], outFormats: [[], [RichFormat.InlineCode], [RichFormat.InlineCode], []], unhandled: 0},
|
||||
{tag: "code", input: "``` ", outText: ["`", "``", "```", " "], outFormats: [[], [], [], []], unhandled: 1},
|
||||
{tag: "strikethrough", input: "~~s~~ ", outText: ["~", "~~", "s", "s~", "s~~", "s "], outFormats: [[], [], [RichFormat.Strikethrough], [RichFormat.Strikethrough], [RichFormat.Strikethrough], []], unhandled: 0},
|
||||
{tag: "underline", input: "_u_ ", outText: ["_", "u", "u_", "u "], outFormats: [[], [RichFormat.Underline], [RichFormat.Underline], []], unhandled: 0},
|
||||
{tag: "multiple closable", input: "***_~~t~~_*** ", outText: ["*", "**", "*", "_", "~", "~~", "t", "t~", "t~~", "t_", "t*", "t**", "t*", "t "], outFormats: [[], [], [RichFormat.Bold], [RichFormat.Bold, RichFormat.Italic], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline, RichFormat.Strikethrough], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline, RichFormat.Strikethrough], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline, RichFormat.Strikethrough], [RichFormat.Bold, RichFormat.Italic, RichFormat.Underline], [RichFormat.Bold, RichFormat.Italic], [RichFormat.Bold, RichFormat.Italic], [RichFormat.Italic], []], unhandled: 0},
|
||||
{tag: "nonclosable closable", input: "* **b** ", outText: ["*", "* ", "*", "**", "b", "b*", "b**", "b "], outFormats: [[], [], [RichFormat.UnorderedList], [RichFormat.UnorderedList], [RichFormat.Bold, RichFormat.UnorderedList], [RichFormat.Bold, RichFormat.UnorderedList], [RichFormat.Bold, RichFormat.UnorderedList], [RichFormat.UnorderedList]], unhandled: 0},
|
||||
{tag: "not at line start", input: " 1) ", outText: [" ", " 1", " 1)", " 1) "], outFormats: [[], [], [], []], unhandled: 0},
|
||||
]
|
||||
}
|
||||
|
||||
function test_textFormat(data): void {
|
||||
spyUnhandledFormat.clear();
|
||||
compare(spyUnhandledFormat.count, 0);
|
||||
|
||||
for (let i = 0; i < data.input.length; i++) {
|
||||
keyClick(data.input[i]);
|
||||
compare(chatMarkdownHelper.checkText(data.outText[i]), true);
|
||||
compare(chatMarkdownHelper.checkFormats(data.outFormats[i]), true);
|
||||
}
|
||||
|
||||
compare(spyUnhandledFormat.count, data.unhandled);
|
||||
}
|
||||
|
||||
function test_backspace(): void {
|
||||
keyClick("*");
|
||||
compare(chatMarkdownHelper.checkText("*"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
keyClick("*");
|
||||
compare(chatMarkdownHelper.checkText("**"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
keyClick("b");
|
||||
compare(chatMarkdownHelper.checkText("b"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("o");
|
||||
compare(chatMarkdownHelper.checkText("bo"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("l");
|
||||
compare(chatMarkdownHelper.checkText("bol"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("d");
|
||||
compare(chatMarkdownHelper.checkText("bold"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick(Qt.Key_Backspace);
|
||||
compare(chatMarkdownHelper.checkText("bol"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick(Qt.Key_Backspace);
|
||||
compare(chatMarkdownHelper.checkText("bo"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("*");
|
||||
compare(chatMarkdownHelper.checkText("bo*"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick("*");
|
||||
compare(chatMarkdownHelper.checkText("bo**"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
keyClick(" ");
|
||||
compare(chatMarkdownHelper.checkText("bo "), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
}
|
||||
|
||||
function test_cursorMove(): void {
|
||||
keyClick("t");
|
||||
keyClick("e");
|
||||
keyClick("s");
|
||||
keyClick("t");
|
||||
compare(chatMarkdownHelper.checkText("test"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
keyClick("*");
|
||||
keyClick("*");
|
||||
keyClick("b");
|
||||
compare(chatMarkdownHelper.checkText("testb"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([RichFormat.Bold]), true);
|
||||
textEdit.cursorPosition = 2;
|
||||
keyClick("*");
|
||||
keyClick("*");
|
||||
keyClick("b");
|
||||
compare(chatMarkdownHelper.checkText("tebstb"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
}
|
||||
|
||||
function test_insertText(): void {
|
||||
textEdit.insert(0, "test");
|
||||
compare(chatMarkdownHelper.checkText("test"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
textEdit.insert(4, "**b");
|
||||
compare(chatMarkdownHelper.checkText("test**b"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
|
||||
textEdit.clear();
|
||||
textEdit.insert(0, "test");
|
||||
compare(chatMarkdownHelper.checkText("test"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
textEdit.insert(2, "**b");
|
||||
compare(chatMarkdownHelper.checkText("te**bst"), true);
|
||||
compare(chatMarkdownHelper.checkFormats([]), true);
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QTextCursor>
|
||||
|
||||
#include "chatmarkdownhelper.h"
|
||||
#include "chattextitemhelper.h"
|
||||
#include "enums/richformat.h"
|
||||
|
||||
class ChatMarkdownHelperTestWrapper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The QML text Item the ChatMerkdownHelper is handling.
|
||||
*/
|
||||
Q_PROPERTY(QQuickItem *textItem READ textItem WRITE setTextItem NOTIFY textItemChanged)
|
||||
|
||||
public:
|
||||
explicit ChatMarkdownHelperTestWrapper(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
, m_chatMarkdownHelper(new ChatMarkdownHelper(this))
|
||||
, m_textItem(new ChatTextItemHelper(this))
|
||||
{
|
||||
m_chatMarkdownHelper->setTextItem(m_textItem);
|
||||
|
||||
connect(m_chatMarkdownHelper, &ChatMarkdownHelper::textItemChanged, this, &ChatMarkdownHelperTestWrapper::textItemChanged);
|
||||
connect(m_chatMarkdownHelper, &ChatMarkdownHelper::unhandledBlockFormat, this, &ChatMarkdownHelperTestWrapper::unhandledBlockFormat);
|
||||
}
|
||||
|
||||
QQuickItem *textItem() const
|
||||
{
|
||||
return m_textItem->textItem();
|
||||
}
|
||||
void setTextItem(QQuickItem *textItem)
|
||||
{
|
||||
m_textItem->setTextItem(textItem);
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool checkText(const QString &text)
|
||||
{
|
||||
const auto doc = m_textItem->document();
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
return text == doc->toPlainText();
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool checkFormats(QList<RichFormat::Format> formats)
|
||||
{
|
||||
const auto cursor = m_textItem->textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return false;
|
||||
}
|
||||
return RichFormat::formatsAtCursor(cursor) == formats;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void clear()
|
||||
{
|
||||
auto cursor = m_textItem->textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return;
|
||||
}
|
||||
cursor.select(QTextCursor::Document);
|
||||
cursor.removeSelectedText();
|
||||
cursor.setBlockCharFormat(RichFormat::charFormatForFormat(RichFormat::Paragraph));
|
||||
cursor.setBlockFormat(RichFormat::blockFormatForFormat(RichFormat::Paragraph));
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void textItemChanged();
|
||||
void unhandledBlockFormat(RichFormat::Format format);
|
||||
|
||||
private:
|
||||
QPointer<ChatMarkdownHelper> m_chatMarkdownHelper;
|
||||
QPointer<ChatTextItemHelper> m_textItem;
|
||||
};
|
||||
@@ -1,301 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtTest
|
||||
|
||||
import org.kde.neochat.libneochat
|
||||
|
||||
import NeoChatTestUtils
|
||||
|
||||
TestCase {
|
||||
name: "ChatTextItemHelperTest"
|
||||
|
||||
TextEdit {
|
||||
id: textEdit
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: textEdit2
|
||||
}
|
||||
|
||||
ChatTextItemHelper {
|
||||
id: textItemHelper
|
||||
|
||||
textItem: textEdit
|
||||
}
|
||||
|
||||
ChatTextItemHelperTestHelper {
|
||||
id: testHelper
|
||||
|
||||
textItem: textItemHelper
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyItem
|
||||
target: textItemHelper
|
||||
signalName: "textItemChanged"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyContentsChanged
|
||||
target: textItemHelper
|
||||
signalName: "contentsChanged"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyContentsChange
|
||||
target: textItemHelper
|
||||
signalName: "contentsChange"
|
||||
}
|
||||
|
||||
SignalSpy {
|
||||
id: spyCursor
|
||||
target: textItemHelper
|
||||
signalName: "cursorPositionChanged"
|
||||
}
|
||||
|
||||
function init(): void {
|
||||
testHelper.setFixedChars("", "");
|
||||
textEdit.clear();
|
||||
textEdit2.clear();
|
||||
spyItem.clear();
|
||||
spyContentsChange.clear();
|
||||
spyContentsChanged.clear();
|
||||
spyCursor.clear();
|
||||
}
|
||||
|
||||
function cleanupTestCase(): void {
|
||||
testHelper.textItem = null;
|
||||
textItemHelper.textItem = null;
|
||||
}
|
||||
|
||||
function test_item(): void {
|
||||
compare(textItemHelper.textItem, textEdit);
|
||||
compare(spyItem.count, 0);
|
||||
textItemHelper.textItem = textEdit2;
|
||||
compare(textItemHelper.textItem, textEdit2);
|
||||
compare(spyItem.count, 1);
|
||||
textItemHelper.textItem = textEdit;
|
||||
compare(textItemHelper.textItem, textEdit);
|
||||
compare(spyItem.count, 2);
|
||||
}
|
||||
|
||||
function test_fixedChars(): void {
|
||||
textEdit.forceActiveFocus();
|
||||
testHelper.setFixedChars("1", "2");
|
||||
compare(textEdit.text, "12");
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
compare(spyCursor.count, 0);
|
||||
keyClick("b");
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 2);
|
||||
compare(spyCursor.count, 1);
|
||||
keyClick(Qt.Key_Left);
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
compare(spyCursor.count, 2);
|
||||
keyClick(Qt.Key_Left);
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
compare(spyCursor.count, 3);
|
||||
keyClick(Qt.Key_Right);
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 2);
|
||||
compare(spyCursor.count, 4);
|
||||
keyClick(Qt.Key_Right);
|
||||
compare(textEdit.text, "1b2");
|
||||
compare(textEdit.cursorPosition, 2);
|
||||
compare(spyCursor.count, 5);
|
||||
}
|
||||
|
||||
function test_document(): void {
|
||||
// We can't get to the QTextDocument from QML so we have to use a helper function.
|
||||
compare(testHelper.compareDocuments(textEdit.textDocument), true);
|
||||
|
||||
textEdit.insert(0, "test text");
|
||||
compare(testHelper.lineCount(), 1);
|
||||
textEdit.insert(textEdit.text.length, "\ntest text");
|
||||
compare(testHelper.lineCount(), 2);
|
||||
textEdit.clear()
|
||||
compare(textEdit.text.length, 0);
|
||||
}
|
||||
|
||||
function test_takeFirstBlock(): void {
|
||||
textEdit.insert(0, "test text");
|
||||
compare(testHelper.firstBlockText(), "test text");
|
||||
compare(textEdit.text.length, 0);
|
||||
textEdit.insert(0, "test text\nmore test text");
|
||||
compare(testHelper.firstBlockText(), "test text");
|
||||
compare(textEdit.text, "more test text");
|
||||
compare(testHelper.firstBlockText(), "more test text");
|
||||
compare(textEdit.text, "");
|
||||
compare(textEdit.text.length, 0);
|
||||
}
|
||||
|
||||
function test_fillFragments(): void {
|
||||
textEdit.insert(0, "before fragment\nmid fragment\nafter fragment");
|
||||
compare(testHelper.checkFragments("before fragment\nmid fragment", "after fragment", ""), true);
|
||||
textEdit.clear();
|
||||
textEdit.insert(0, "before fragment\nmid fragment\nafter fragment");
|
||||
textEdit.cursorPosition = 16;
|
||||
compare(testHelper.checkFragments("before fragment", "mid fragment", "after fragment"), true);
|
||||
textEdit.clear();
|
||||
textEdit.insert(0, "before fragment\nmid fragment\nafter fragment");
|
||||
textEdit.cursorPosition = 29;
|
||||
compare(testHelper.checkFragments("before fragment\nmid fragment", "after fragment", ""), true);
|
||||
textEdit.clear();
|
||||
}
|
||||
|
||||
function test_insertFragment(): void {
|
||||
testHelper.insertFragment("test text");
|
||||
compare(textEdit.text, "test text");
|
||||
compare(textEdit.cursorPosition, 9);
|
||||
testHelper.insertFragment("beginning ", 1);
|
||||
compare(textEdit.text, "beginning test text");
|
||||
compare(textEdit.cursorPosition, 10);
|
||||
testHelper.insertFragment(" end", 2);
|
||||
compare(textEdit.text, "beginning test text end");
|
||||
compare(textEdit.cursorPosition, 23);
|
||||
textEdit.clear();
|
||||
|
||||
testHelper.insertFragment("test text", 0, true);
|
||||
compare(textEdit.text, "test text");
|
||||
compare(textEdit.cursorPosition, 0);
|
||||
}
|
||||
|
||||
function test_cursor(): void {
|
||||
// We can't get to the QTextCursor from QML so we have to use a helper function.
|
||||
compare(testHelper.compareCursor(textEdit.cursorPosition, textEdit.selectionStart, textEdit.selectionEnd), true);
|
||||
compare(textEdit.cursorPosition, testHelper.cursorPosition());
|
||||
// Check we get the appropriate content and cursor change signals when inserting text.
|
||||
textEdit.insert(0, "test text")
|
||||
compare(spyContentsChange.count, 1);
|
||||
compare(spyContentsChange.signalArguments[0][0], 0);
|
||||
compare(spyContentsChange.signalArguments[0][1], 0);
|
||||
compare(spyContentsChange.signalArguments[0][2], 9);
|
||||
compare(spyContentsChanged.count, 1);
|
||||
compare(spyCursor.count, 1);
|
||||
compare(spyCursor.signalArguments[0][0], true);
|
||||
compare(testHelper.compareCursor(textEdit.cursorPosition, textEdit.selectionStart, textEdit.selectionEnd), true);
|
||||
compare(textEdit.cursorPosition, testHelper.cursorPosition());
|
||||
// Check we get only get a cursor change signal when moving the cursor.
|
||||
textEdit.cursorPosition = 4;
|
||||
compare(spyContentsChanged.count, 1);
|
||||
compare(spyCursor.count, 2);
|
||||
compare(spyCursor.signalArguments[1][0], false);
|
||||
textEdit.selectAll();
|
||||
compare(spyContentsChanged.count, 1);
|
||||
compare(spyCursor.count, 2);
|
||||
compare(testHelper.compareCursor(textEdit.cursorPosition, textEdit.selectionStart, textEdit.selectionEnd), true);
|
||||
compare(textEdit.cursorPosition, testHelper.cursorPosition());
|
||||
// Check we get the appropriate content and cursor change signals when removing text.
|
||||
textEdit.clear();
|
||||
compare(spyContentsChange.count, 2);
|
||||
compare(spyContentsChange.signalArguments[1][0], 0);
|
||||
compare(spyContentsChange.signalArguments[1][1], 9);
|
||||
compare(spyContentsChange.signalArguments[1][2], 0);
|
||||
compare(spyContentsChanged.count, 2);
|
||||
compare(spyCursor.count, 3);
|
||||
compare(spyCursor.signalArguments[2][0], true);
|
||||
}
|
||||
|
||||
function test_setCursor(): void {
|
||||
textEdit.insert(0, "test text");
|
||||
compare(textEdit.cursorPosition, 9);
|
||||
compare(spyCursor.count, 1);
|
||||
testHelper.setCursorPosition(5);
|
||||
compare(textEdit.cursorPosition, 5);
|
||||
compare(spyCursor.count, 2);
|
||||
testHelper.setCursorPosition(1);
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
compare(spyCursor.count, 3);
|
||||
|
||||
textEdit.cursorVisible = false;
|
||||
compare(textEdit.cursorVisible, false);
|
||||
testHelper.setCursorVisible(true);
|
||||
compare(textEdit.cursorVisible, true);
|
||||
testHelper.setCursorVisible(false);
|
||||
compare(textEdit.cursorVisible, false);
|
||||
}
|
||||
|
||||
function test_setCursorFromTextItem(): void {
|
||||
textEdit.insert(0, "line 1\nline 2");
|
||||
textEdit2.insert(0, "line 1\nline 2");
|
||||
testHelper.setCursorFromTextItem(textEdit2, false, 0);
|
||||
compare(textEdit.cursorPosition, 7);
|
||||
testHelper.setCursorFromTextItem(textEdit2, true, 7);
|
||||
compare(textEdit.cursorPosition, 0);
|
||||
testHelper.setCursorFromTextItem(textEdit2, false, 1);
|
||||
compare(textEdit.cursorPosition, 8);
|
||||
testHelper.setCursorFromTextItem(textEdit2, true, 8);
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
|
||||
testHelper.setFixedChars("1", "2");
|
||||
testHelper.setCursorFromTextItem(textEdit2, false, 0);
|
||||
compare(textEdit.cursorPosition, 8);
|
||||
testHelper.setCursorFromTextItem(textEdit2, true, 7);
|
||||
compare(textEdit.cursorPosition, 1);
|
||||
}
|
||||
|
||||
function test_mergeFormat(): void {
|
||||
textEdit.insert(0, "lots of text");
|
||||
testHelper.setCursorPosition(0);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Bold);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Bold]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Italic);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Bold, RichFormat.Italic]), true);
|
||||
testHelper.setCursorPosition(6);
|
||||
compare(testHelper.checkFormatsAtCursor([]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Underline);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Underline]), true);
|
||||
testHelper.setCursorPosition(9);
|
||||
compare(testHelper.checkFormatsAtCursor([]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Strikethrough);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Strikethrough]), true);
|
||||
textEdit.clear();
|
||||
|
||||
textEdit.insert(0, "heading");
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Heading1);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Bold, RichFormat.Heading1]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Heading2);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.Bold, RichFormat.Heading2]), true);
|
||||
testHelper.mergeFormatOnCursor(RichFormat.Paragraph);
|
||||
compare(testHelper.checkFormatsAtCursor([]), true);
|
||||
textEdit.clear();
|
||||
|
||||
textEdit.insert(0, "text");
|
||||
testHelper.mergeFormatOnCursor(RichFormat.UnorderedList);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.UnorderedList]), true);
|
||||
compare(testHelper.markdownText(), "- text");
|
||||
testHelper.mergeFormatOnCursor(RichFormat.OrderedList);
|
||||
compare(testHelper.checkFormatsAtCursor([RichFormat.OrderedList]), true);
|
||||
compare(testHelper.markdownText(), "1. text");
|
||||
textEdit.clear();
|
||||
}
|
||||
|
||||
function test_list(): void {
|
||||
compare(testHelper.canIndentListMoreAtCursor(), true);
|
||||
testHelper.indentListMoreAtCursor();
|
||||
compare(testHelper.canIndentListMoreAtCursor(), true);
|
||||
testHelper.indentListMoreAtCursor();
|
||||
compare(testHelper.canIndentListMoreAtCursor(), true);
|
||||
testHelper.indentListMoreAtCursor();
|
||||
compare(testHelper.canIndentListMoreAtCursor(), false);
|
||||
|
||||
compare(testHelper.canIndentListLessAtCursor(), true);
|
||||
testHelper.indentListLessAtCursor();
|
||||
compare(testHelper.canIndentListLessAtCursor(), true);
|
||||
testHelper.indentListLessAtCursor();
|
||||
compare(testHelper.canIndentListLessAtCursor(), true);
|
||||
testHelper.indentListLessAtCursor();
|
||||
compare(testHelper.canIndentListLessAtCursor(), false);
|
||||
}
|
||||
|
||||
function test_forceActiveFocus(): void {
|
||||
textEdit2.forceActiveFocus();
|
||||
compare(textEdit.activeFocus, false);
|
||||
testHelper.forceActiveFocus();
|
||||
compare(textEdit.activeFocus, true);
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextCursor>
|
||||
#include <QTextDocumentFragment>
|
||||
|
||||
#include "chattextitemhelper.h"
|
||||
|
||||
class ChatTextItemHelperTestHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The QML text Item the TextItemHelper is handling.
|
||||
*/
|
||||
Q_PROPERTY(ChatTextItemHelper *textItem READ textItem WRITE setTextItem NOTIFY textItemChanged)
|
||||
|
||||
public:
|
||||
explicit ChatTextItemHelperTestHelper(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ChatTextItemHelper *textItem() const
|
||||
{
|
||||
return m_textItem;
|
||||
}
|
||||
void setTextItem(ChatTextItemHelper *textItem)
|
||||
{
|
||||
if (textItem == m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem = textItem;
|
||||
Q_EMIT textItemChanged();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void setFixedChars(const QString &startChars, const QString &endChars)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->setFixedChars(startChars, endChars);
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool compareDocuments(QQuickTextDocument *document)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
return document->textDocument() == m_textItem->document();
|
||||
}
|
||||
|
||||
Q_INVOKABLE int lineCount()
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return -1;
|
||||
}
|
||||
return m_textItem->lineCount();
|
||||
}
|
||||
|
||||
Q_INVOKABLE QString firstBlockText()
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return {};
|
||||
}
|
||||
return m_textItem->takeFirstBlock().toPlainText();
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool checkFragments(const QString &before, const QString &mid, const QString &after)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasBefore = false;
|
||||
QTextDocumentFragment midFragment;
|
||||
std::optional<QTextDocumentFragment> afterFragment = std::nullopt;
|
||||
m_textItem->fillFragments(hasBefore, midFragment, afterFragment);
|
||||
|
||||
return hasBefore && m_textItem->document()->toPlainText() == before && midFragment.toPlainText() == mid && after.isEmpty()
|
||||
? !afterFragment
|
||||
: afterFragment->toPlainText() == after;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void insertFragment(const QString &text, ChatTextItemHelper::InsertPosition position = ChatTextItemHelper::Cursor, bool keepPosition = false)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
const auto fragment = QTextDocumentFragment::fromPlainText(text);
|
||||
m_textItem->insertFragment(fragment, position, keepPosition);
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool compareCursor(int cursorPosition, int selectionStart, int selectionEnd)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
const auto cursor = m_textItem->textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return false;
|
||||
}
|
||||
const auto posSame = cursor.position() == cursorPosition;
|
||||
const auto startSame = cursor.selectionStart() == selectionStart;
|
||||
const auto endSame = cursor.selectionEnd() == selectionEnd;
|
||||
return posSame && startSame && endSame;
|
||||
}
|
||||
|
||||
Q_INVOKABLE int cursorPosition() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return -1;
|
||||
}
|
||||
return *m_textItem->cursorPosition();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void setCursorPosition(int pos)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->setCursorPosition(pos);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void setCursorVisible(bool visible)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->setCursorVisible(visible);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void setCursorFromTextItem(QQuickItem *item, bool infront, int cursorPos)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
const auto textItem = new ChatTextItemHelper(this);
|
||||
textItem->setTextItem(item);
|
||||
textItem->setCursorPosition(cursorPos);
|
||||
m_textItem->setCursorFromTextItem(textItem, infront);
|
||||
textItem->deleteLater();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void mergeFormatOnCursor(RichFormat::Format format)
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->mergeFormatOnCursor(format);
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool checkFormatsAtCursor(QList<RichFormat::Format> formats)
|
||||
{
|
||||
const auto cursor = m_textItem->textCursor();
|
||||
if (cursor.isNull()) {
|
||||
return false;
|
||||
}
|
||||
return RichFormat::formatsAtCursor(cursor) == formats;
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool canIndentListMoreAtCursor() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
return m_textItem->canIndentListMoreAtCursor();
|
||||
}
|
||||
Q_INVOKABLE bool canIndentListLessAtCursor() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return false;
|
||||
}
|
||||
return m_textItem->canIndentListLessAtCursor();
|
||||
}
|
||||
Q_INVOKABLE void indentListMoreAtCursor()
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->indentListMoreAtCursor();
|
||||
}
|
||||
Q_INVOKABLE void indentListLessAtCursor()
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->indentListLessAtCursor();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void forceActiveFocus() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return;
|
||||
}
|
||||
m_textItem->forceActiveFocus();
|
||||
}
|
||||
|
||||
Q_INVOKABLE QString markdownText() const
|
||||
{
|
||||
if (!m_textItem) {
|
||||
return {};
|
||||
}
|
||||
return m_textItem->markdownText();
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void textItemChanged();
|
||||
|
||||
private:
|
||||
QPointer<ChatTextItemHelper> m_textItem;
|
||||
};
|
||||
20
autotests/data/test-min-sync-extra-sync.json
Normal file
20
autotests/data/test-min-sync-extra-sync.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"state": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"membership": "leave"
|
||||
},
|
||||
"event_id": "$1432735824666PhrSA:example.org",
|
||||
"origin_server_ts": 1432735824666,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@example:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"replaces_state": "$143273582443PhrSn:example.org"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ private Q_SLOTS:
|
||||
void nullSingleLineDisplayName();
|
||||
void time();
|
||||
void nullTime();
|
||||
void timeString();
|
||||
void highlighted();
|
||||
void nullHighlighted();
|
||||
void hidden();
|
||||
@@ -99,12 +100,12 @@ void EventHandlerTest::time()
|
||||
{
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
|
||||
QCOMPARE(EventHandler::dateTime(room, event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
|
||||
QCOMPARE(EventHandler::time(room, event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
|
||||
|
||||
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
|
||||
QCOMPARE(room->pendingEvents().size(), 1);
|
||||
const auto pendingIt = room->findPendingEvent(txID);
|
||||
QCOMPARE(EventHandler::dateTime(room, pendingIt->event(), true), pendingIt->lastUpdated());
|
||||
QCOMPARE(EventHandler::time(room, pendingIt->event(), true), pendingIt->lastUpdated());
|
||||
|
||||
room->discardMessage(txID);
|
||||
QCOMPARE(room->pendingEvents().size(), 0);
|
||||
@@ -113,10 +114,40 @@ void EventHandlerTest::time()
|
||||
void EventHandlerTest::nullTime()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "time called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::dateTime(nullptr, nullptr), QDateTime());
|
||||
QCOMPARE(EventHandler::time(nullptr, nullptr), QDateTime());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "time called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::dateTime(room, nullptr), QDateTime());
|
||||
QCOMPARE(EventHandler::time(room, nullptr), QDateTime());
|
||||
}
|
||||
|
||||
void EventHandlerTest::timeString()
|
||||
{
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
|
||||
KFormat format;
|
||||
|
||||
QCOMPARE(EventHandler::timeString(room, event, false),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, event, true),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, event, u"hh:mm"_s),
|
||||
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::LocalTime)).toString(u"hh:mm"_s));
|
||||
|
||||
const auto txID = room->postJson("m.room.message"_L1, event->fullJson());
|
||||
QCOMPARE(room->pendingEvents().size(), 1);
|
||||
const auto pendingIt = room->findPendingEvent(txID);
|
||||
|
||||
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::ShortFormat, true),
|
||||
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::ShortFormat, true),
|
||||
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), false, QLocale::LongFormat, true),
|
||||
QLocale().toString(pendingIt->lastUpdated().toLocalTime().time(), QLocale::LongFormat));
|
||||
QCOMPARE(EventHandler::timeString(room, pendingIt->event(), true, QLocale::LongFormat, true),
|
||||
format.formatRelativeDate(pendingIt->lastUpdated().toLocalTime().date(), QLocale::LongFormat));
|
||||
|
||||
room->discardMessage(txID);
|
||||
QCOMPARE(room->pendingEvents().size(), 0);
|
||||
}
|
||||
|
||||
void EventHandlerTest::highlighted()
|
||||
|
||||
@@ -19,7 +19,13 @@ class LinkPreviewerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void linkPreviewsMatch_data();
|
||||
void linkPreviewsMatch();
|
||||
|
||||
@@ -30,6 +36,12 @@ private Q_SLOTS:
|
||||
void linkPreviewsReject();
|
||||
};
|
||||
|
||||
void LinkPreviewerTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(u"@bob:example.org"_s);
|
||||
room = new TestUtils::TestRoom(connection, u"!test:example.org"_s);
|
||||
}
|
||||
|
||||
void LinkPreviewerTest::linkPreviewsMatch_data()
|
||||
{
|
||||
QTest::addColumn<QString>("inputString");
|
||||
|
||||
@@ -1,620 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QAbstractItemModelTester>
|
||||
#include <QObject>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
#include <QVariantList>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "contentprovider.h"
|
||||
#include "enums/powerlevel.h"
|
||||
#include "enums/roomsortparameter.h"
|
||||
#include "models/accountemoticonmodel.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/commonroomsmodel.h"
|
||||
#include "models/completionmodel.h"
|
||||
#include "models/completionproxymodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/devicesmodel.h"
|
||||
#include "models/devicesproxymodel.h"
|
||||
#include "models/emojimodel.h"
|
||||
#include "models/emoticonfiltermodel.h"
|
||||
#include "models/eventmessagecontentmodel.h"
|
||||
#include "models/imagepacksmodel.h"
|
||||
#include "models/linemodel.h"
|
||||
#include "models/livelocationsmodel.h"
|
||||
#include "models/locationsmodel.h"
|
||||
#include "models/messagecontentfiltermodel.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
#include "models/notificationsmodel.h"
|
||||
#include "models/permissionsmodel.h"
|
||||
#include "models/pinnedmessagemodel.h"
|
||||
#include "models/pollanswermodel.h"
|
||||
#include "models/publicroomlistmodel.h"
|
||||
#include "models/pushrulemodel.h"
|
||||
#include "models/readmarkermodel.h"
|
||||
#include "models/roomsortparametermodel.h"
|
||||
#include "models/searchmodel.h"
|
||||
#include "models/serverlistmodel.h"
|
||||
#include "models/spacechildrenmodel.h"
|
||||
#include "models/spacechildsortfiltermodel.h"
|
||||
#include "models/statefiltermodel.h"
|
||||
#include "models/statekeysmodel.h"
|
||||
#include "models/statemodel.h"
|
||||
#include "models/stickermodel.h"
|
||||
#include "models/threadmodel.h"
|
||||
#include "models/threepidmodel.h"
|
||||
#include "models/userdirectorylistmodel.h"
|
||||
#include "models/userfiltermodel.h"
|
||||
#include "models/webshortcutmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "pollhandler.h"
|
||||
#include "roommanager.h"
|
||||
#include "server.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
// TODO: Add data to all models as relevant.
|
||||
|
||||
// Performs basic tests on all models in NeoChat
|
||||
// When adding a new test, create the model first, then the tester, then initialize the model (e.g., setConnection and setRoom).
|
||||
// That way, the models are also tested for whether they can handle having no connection etc.
|
||||
class ModelTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
NeoChatConnection *connection = nullptr;
|
||||
NeoChatRoom *room = nullptr;
|
||||
|
||||
QString eventId;
|
||||
|
||||
Server server;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void testRoomTreeModel();
|
||||
void testMessageContentModel();
|
||||
void testEventMessageContentModel();
|
||||
void testThreadModel();
|
||||
void testThreadFetchModel();
|
||||
void testThreadChatBarModel();
|
||||
void testReactionModel();
|
||||
void testPollAnswerModel();
|
||||
void testLineModel();
|
||||
void testSpaceChildrenModel();
|
||||
void testItineraryModel();
|
||||
void testPublicRoomListModel();
|
||||
void testMessageFilterModel();
|
||||
void testThreePIdModel();
|
||||
void testMediaMessageFilterModel();
|
||||
void testWebshortcutModel();
|
||||
void testTimelineMessageModel();
|
||||
void testReadMarkerModel();
|
||||
void testSearchModel();
|
||||
void testStateModel();
|
||||
void testTimelineModel();
|
||||
void testStateKeysModel();
|
||||
void testPinnedMessageModel();
|
||||
void testUserListModel();
|
||||
void testStickerModel();
|
||||
void testPowerLevelModel();
|
||||
void testImagePacksModel();
|
||||
void testCompletionModel();
|
||||
void testRoomListModel();
|
||||
void testCommonRoomsModel();
|
||||
void testNotificationsModel();
|
||||
void testLocationsModel();
|
||||
void testServerListModel();
|
||||
void testEmojiModel();
|
||||
void testCustomEmojiModel();
|
||||
void testPushRuleModel();
|
||||
void testActionsModel();
|
||||
void testDevicesModel();
|
||||
void testUserDirectoryListModel();
|
||||
void testAccountEmoticonModel();
|
||||
void testPermissionsModel();
|
||||
void testLiveLocationsModel();
|
||||
void testRoomSortParameterModel();
|
||||
void testSortFilterRoomTreeModel();
|
||||
void testSortFilterSpaceListModel();
|
||||
void testSortFilterRoomListModel();
|
||||
void testSpaceChildSortFilterModel();
|
||||
void testStateFilterModel();
|
||||
void testMessageContentFilterModel();
|
||||
void testUserFilterModel();
|
||||
void testEmoticonFilterModel();
|
||||
void testDevicesProxyModel();
|
||||
void testCompletionProxyModel();
|
||||
};
|
||||
|
||||
void ModelTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true, this);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
const auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
eventId = server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
|
||||
server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"asdf"_s},
|
||||
{u"m.relates_to"_s,
|
||||
QJsonObject{
|
||||
{u"event_id"_s, u"$GEucSt3TfVl6DVpKEyeOlRsXzjLv2ZCVgSQuQclFg1o"_s},
|
||||
{u"is_falling_back"_s, true},
|
||||
{u"m.in_reply_to"_s, QJsonObject{{u"event_id"_s, u"$GEucSt3TfVl6DVpKEyeOlRsXzjLv2ZCVgSQuQclFg1o"_s}}},
|
||||
{u"rel_type"_s, u"m.thread"_s},
|
||||
}},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
}
|
||||
|
||||
void ModelTest::testRoomTreeModel()
|
||||
{
|
||||
auto roomTreeModel = new RoomTreeModel(this);
|
||||
auto tester = new QAbstractItemModelTester(roomTreeModel, roomTreeModel);
|
||||
tester->setUseFetchMore(true);
|
||||
roomTreeModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testMessageContentModel()
|
||||
{
|
||||
auto contentModel = std::make_unique<MessageContentModel>(room, eventId);
|
||||
auto tester = new QAbstractItemModelTester(contentModel.get(), contentModel.get());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testEventMessageContentModel()
|
||||
{
|
||||
auto model = std::make_unique<EventMessageContentModel>(room, eventId);
|
||||
auto tester = new QAbstractItemModelTester(model.get(), model.get());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testThreadModel()
|
||||
{
|
||||
auto model = new ThreadModel(eventId, room);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testThreadFetchModel()
|
||||
{
|
||||
auto model = new ThreadFetchModel(new ThreadModel(eventId, room));
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testThreadChatBarModel()
|
||||
{
|
||||
auto model = new ThreadChatBarModel(new ThreadModel(eventId, room), room);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testReactionModel()
|
||||
{
|
||||
auto messageContentModel = std::make_unique<MessageContentModel>(room);
|
||||
auto model = new ReactionModel(messageContentModel.get(), eventId, room);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testPollAnswerModel()
|
||||
{
|
||||
auto handler = std::make_unique<PollHandler>(room, eventId);
|
||||
auto model = new PollAnswerModel(handler.get());
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testLineModel()
|
||||
{
|
||||
auto model = new LineModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto document = new QTextDocument(this);
|
||||
model->setDocument(document);
|
||||
document->setPlainText(u"foo\nbar\n\nbaz"_s);
|
||||
}
|
||||
|
||||
void ModelTest::testSpaceChildrenModel()
|
||||
{
|
||||
auto model = new SpaceChildrenModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSpace(room);
|
||||
}
|
||||
|
||||
void ModelTest::testItineraryModel()
|
||||
{
|
||||
auto model = new ItineraryModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testPublicRoomListModel()
|
||||
{
|
||||
auto model = new PublicRoomListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testMessageFilterModel()
|
||||
{
|
||||
auto timelineModel = new TimelineModel(this);
|
||||
auto model = new MessageFilterModel(this, timelineModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
timelineModel->setRoom(room);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testThreePIdModel()
|
||||
{
|
||||
auto model = new ThreePIdModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testMediaMessageFilterModel()
|
||||
{
|
||||
auto timelineModel = new TimelineModel(this);
|
||||
auto messageFilterModel = new MessageFilterModel(this, timelineModel);
|
||||
auto model = new MediaMessageFilterModel(this, messageFilterModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
timelineModel->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testWebshortcutModel()
|
||||
{
|
||||
auto model = new WebShortcutModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSelectedText(u"Foo"_s);
|
||||
}
|
||||
|
||||
void ModelTest::testTimelineMessageModel()
|
||||
{
|
||||
auto model = new TimelineMessageModel();
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testReadMarkerModel()
|
||||
{
|
||||
auto model = std::make_unique<ReadMarkerModel>(eventId, room);
|
||||
auto tester = new QAbstractItemModelTester(model.get(), model.get());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testSearchModel()
|
||||
{
|
||||
auto model = new SearchModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSearchText(u"foo"_s);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testStateModel()
|
||||
{
|
||||
auto model = new StateModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testTimelineModel()
|
||||
{
|
||||
auto model = new TimelineModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testStateKeysModel()
|
||||
{
|
||||
auto model = new StateKeysModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setEventType(u"m.room.member"_s);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testPinnedMessageModel()
|
||||
{
|
||||
auto model = new PinnedMessageModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testUserListModel()
|
||||
{
|
||||
auto model = new UserListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testStickerModel()
|
||||
{
|
||||
auto model = new StickerModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setPackIndex(0);
|
||||
model->setRoom(room);
|
||||
auto imagePacksModel = new ImagePacksModel(this);
|
||||
model->setModel(imagePacksModel);
|
||||
imagePacksModel->setRoom(room);
|
||||
imagePacksModel->setShowEmoticons(true);
|
||||
imagePacksModel->setShowStickers(true);
|
||||
}
|
||||
|
||||
void ModelTest::testPowerLevelModel()
|
||||
{
|
||||
auto model = new PowerLevelModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testImagePacksModel()
|
||||
{
|
||||
auto model = new ImagePacksModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
model->setShowEmoticons(true);
|
||||
model->setShowStickers(true);
|
||||
}
|
||||
|
||||
void ModelTest::testCompletionModel()
|
||||
{
|
||||
auto model = new CompletionModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
model->setAutoCompletionType(CompletionModel::Room);
|
||||
auto roomListModel = new RoomListModel(this);
|
||||
roomListModel->setConnection(connection);
|
||||
model->setRoomListModel(roomListModel);
|
||||
}
|
||||
|
||||
void ModelTest::testRoomListModel()
|
||||
{
|
||||
auto model = new RoomListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testCommonRoomsModel()
|
||||
{
|
||||
auto model = new CommonRoomsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
model->setUserId(u"@user:example.com"_s);
|
||||
}
|
||||
|
||||
void ModelTest::testNotificationsModel()
|
||||
{
|
||||
auto model = new NotificationsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testLocationsModel()
|
||||
{
|
||||
auto model = new LocationsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testServerListModel()
|
||||
{
|
||||
auto model = new ServerListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testEmojiModel()
|
||||
{
|
||||
auto tester = new QAbstractItemModelTester(&EmojiModel::instance(), &EmojiModel::instance());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testCustomEmojiModel()
|
||||
{
|
||||
auto tester = new QAbstractItemModelTester(&CustomEmojiModel::instance(), &CustomEmojiModel::instance());
|
||||
tester->setUseFetchMore(true);
|
||||
CustomEmojiModel::instance().setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testPushRuleModel()
|
||||
{
|
||||
auto model = new PushRuleModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testActionsModel()
|
||||
{
|
||||
auto tester = new QAbstractItemModelTester(&ActionsModel::instance(), &ActionsModel::instance());
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testDevicesModel()
|
||||
{
|
||||
auto model = new DevicesModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testUserDirectoryListModel()
|
||||
{
|
||||
auto model = new UserDirectoryListModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
model->setSearchText(u"foo"_s);
|
||||
}
|
||||
|
||||
void ModelTest::testAccountEmoticonModel()
|
||||
{
|
||||
auto model = new AccountEmoticonModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testPermissionsModel()
|
||||
{
|
||||
auto model = new PermissionsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testLiveLocationsModel()
|
||||
{
|
||||
auto model = new LiveLocationsModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testRoomSortParameterModel()
|
||||
{
|
||||
auto model = new RoomSortParameterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
}
|
||||
|
||||
void ModelTest::testSortFilterRoomTreeModel()
|
||||
{
|
||||
auto sourceModel = new RoomTreeModel(this);
|
||||
auto model = new SortFilterRoomTreeModel(sourceModel, sourceModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
sourceModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testSortFilterSpaceListModel()
|
||||
{
|
||||
auto sourceModel = new RoomListModel(this);
|
||||
auto model = new SortFilterSpaceListModel(sourceModel, sourceModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
sourceModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testSortFilterRoomListModel()
|
||||
{
|
||||
auto sourceModel = new RoomListModel(this);
|
||||
auto model = new SortFilterRoomListModel(sourceModel, sourceModel);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
sourceModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testSpaceChildSortFilterModel()
|
||||
{
|
||||
auto model = new SpaceChildSortFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto spaceChildrenModel = new SpaceChildrenModel(this);
|
||||
model->setSourceModel(spaceChildrenModel);
|
||||
spaceChildrenModel->setSpace(nullptr);
|
||||
}
|
||||
|
||||
void ModelTest::testStateFilterModel()
|
||||
{
|
||||
auto model = new StateFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto stateModel = new StateModel(this);
|
||||
model->setSourceModel(stateModel);
|
||||
stateModel->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testMessageContentFilterModel()
|
||||
{
|
||||
auto model = new MessageContentFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSourceModel(ContentProvider::self().contentModelForEvent(room, eventId));
|
||||
}
|
||||
|
||||
void ModelTest::testUserFilterModel()
|
||||
{
|
||||
auto model = new UserFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto userListModel = new UserListModel(this);
|
||||
model->setSourceModel(userListModel);
|
||||
userListModel->setRoom(room);
|
||||
}
|
||||
|
||||
void ModelTest::testEmoticonFilterModel()
|
||||
{
|
||||
auto model = new EmoticonFilterModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto accountEmoticonModel = new AccountEmoticonModel(this);
|
||||
model->setSourceModel(accountEmoticonModel);
|
||||
model->setShowEmojis(true);
|
||||
model->setShowStickers(true);
|
||||
accountEmoticonModel->setConnection(connection);
|
||||
}
|
||||
|
||||
void ModelTest::testDevicesProxyModel()
|
||||
{
|
||||
auto model = new DevicesProxyModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
auto devicesModel = new DevicesModel(this);
|
||||
model->setSourceModel(devicesModel);
|
||||
devicesModel->setConnection(dynamic_cast<NeoChatConnection *>(connection));
|
||||
}
|
||||
|
||||
void ModelTest::testCompletionProxyModel()
|
||||
{
|
||||
auto model = new CompletionProxyModel(this);
|
||||
auto tester = new QAbstractItemModelTester(model, model);
|
||||
tester->setUseFetchMore(true);
|
||||
model->setSourceModel(&EmojiModel::instance());
|
||||
}
|
||||
|
||||
QTEST_MAIN(ModelTest)
|
||||
#include "modeltest.moc"
|
||||
@@ -9,10 +9,6 @@
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "server.h"
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -22,8 +18,7 @@ class NeoChatRoomTest : public QObject {
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
NeoChatRoom *room = nullptr;
|
||||
Server server;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
@@ -32,27 +27,8 @@ private Q_SLOTS:
|
||||
|
||||
void NeoChatRoomTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true, this);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
|
||||
const auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
|
||||
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
|
||||
}
|
||||
|
||||
void NeoChatRoomTest::eventTest()
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <quicktest.h>
|
||||
|
||||
QUICK_TEST_MAIN(NeoChat)
|
||||
@@ -127,7 +127,7 @@ void Server::start()
|
||||
qFatal() << "Server failed to listen on a port.";
|
||||
return;
|
||||
} else {
|
||||
qInfo() << "Server listening";
|
||||
qWarning() << "Server listening";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,25 +203,6 @@ QString Server::sendEvent(const QString &roomId, const QString &eventType, const
|
||||
return eventId;
|
||||
}
|
||||
|
||||
QString Server::sendStateEvent(const QString &roomId, const QString &eventType, const QString &stateKey, const QJsonObject &content)
|
||||
{
|
||||
Changes changes;
|
||||
const auto eventId = generateEventId();
|
||||
const auto json = QJsonObject{{u"type"_s, eventType},
|
||||
{u"content"_s, content},
|
||||
{u"sender"_s, u"@foo:server.com"_s},
|
||||
{u"event_id"_s, eventId},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"state_key"_s, stateKey}};
|
||||
changes.events += Changes::Event{
|
||||
.fullJson = json,
|
||||
};
|
||||
changes.stateEvents += Changes::Event{.fullJson = json};
|
||||
m_state += changes;
|
||||
return eventId;
|
||||
}
|
||||
|
||||
void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &responder)
|
||||
{
|
||||
QJsonObject joinRooms;
|
||||
@@ -353,18 +334,6 @@ void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &respo
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &state : change.stateEvents) {
|
||||
const auto &roomId = state.fullJson[u"room_id"_s].toString();
|
||||
// TODO: The join could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents.append(state.fullJson);
|
||||
auto room = joinRooms[roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[roomId] = room;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &event : change.events) {
|
||||
// TODO the room might be in a different join state.
|
||||
@@ -397,5 +366,6 @@ void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &respo
|
||||
syncData[u"rooms"_s] = rooms;
|
||||
}
|
||||
|
||||
qWarning() << syncData;
|
||||
responder.write(QJsonDocument(syncData), QHttpServerResponder::StatusCode::Ok);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ struct Changes {
|
||||
QJsonObject fullJson;
|
||||
};
|
||||
QList<Event> events;
|
||||
QList<Event> stateEvents;
|
||||
};
|
||||
|
||||
struct RoomData {
|
||||
@@ -68,7 +67,6 @@ public:
|
||||
*/
|
||||
QString createServerNoticesRoom(const QString &matrixId);
|
||||
QString sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content);
|
||||
QString sendStateEvent(const QString &roomId, const QString &eventType, const QString &stateKey, const QJsonObject &content);
|
||||
|
||||
private:
|
||||
QHttpServer m_server;
|
||||
|
||||
@@ -208,7 +208,7 @@ void TimelineMessageModelTest::idToRow()
|
||||
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
|
||||
model->setRoom(room);
|
||||
|
||||
QCOMPARE(model->indexForEventId(u"$153456789:example.org"_s).row(), 0);
|
||||
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0);
|
||||
}
|
||||
|
||||
void TimelineMessageModelTest::cleanup()
|
||||
|
||||
@@ -193,7 +193,6 @@
|
||||
<li xml:lang="ar">التصويت - MSC3381</li>
|
||||
<li xml:lang="ca">Votacions - MSC3381</li>
|
||||
<li xml:lang="ca-valencia">Votacions - MSC3381</li>
|
||||
<li xml:lang="de">Umfragen – MSC3381</li>
|
||||
<li xml:lang="el">Δημοσκοπήσεις - MSC3381</li>
|
||||
<li xml:lang="en-GB">Polls - MSC3381</li>
|
||||
<li xml:lang="eo">Enketoj - MSC3381</li>
|
||||
@@ -228,7 +227,6 @@
|
||||
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
|
||||
<li xml:lang="ca">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="ca-valencia">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="de">Sticker-Pakete – MSC2545</li>
|
||||
<li xml:lang="el">Πακέτα αυτοκόλλητων - MSC2545</li>
|
||||
<li xml:lang="en-GB">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="eo">Glumark-Pakoj - MSC2545</li>
|
||||
@@ -489,8 +487,6 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="25.12.2" date="2026-02-05"/>
|
||||
<release version="25.12.1" date="2026-01-08"/>
|
||||
<release version="25.12.0" date="2025-12-11"/>
|
||||
<release version="25.08.3" date="2025-11-06"/>
|
||||
<release version="25.08.2" date="2025-10-09"/>
|
||||
@@ -517,89 +513,16 @@
|
||||
<url>https://kde.org/announcements/megarelease/6/#neochat</url>
|
||||
<description>
|
||||
<p>In the newest version, when launching the app, you will get a welcome page that lets you choose which account you want to use and lets you log in to other accounts. The welcome screen will also warn you when NeoChat cannot load an account.</p>
|
||||
<p xml:lang="ar">في الإصدار الأحدث، عند تشغيل التطبيق، ستظهر صفحة ترحيب تتيح اختيار الحساب المراد استخدامه وتسمح بالولوج إلى حسابات أخرى. كما تحذر شاشة الترحيب عندما يتعذر على نيوتشات تحميل حساب ما.</p>
|
||||
<p xml:lang="ca">En la versió més recent, en llançar l'aplicació, obtindreu una pàgina de benvinguda que us permetrà triar quin compte voleu utilitzar i permetrà iniciar sessió en altres comptes. La pantalla de benvinguda també us avisarà quan el NeoChat no pugui carregar un compte.</p>
|
||||
<p xml:lang="ca-valencia">En la versió més recent, en iniciar l'aplicació, obtindreu una pàgina de benvinguda que us permetrà triar quin compte voleu utilitzar i permetrà iniciar sessió en altres comptes. La pantalla de benvinguda també vos avisarà quan NeoChat no puga carregar un compte.</p>
|
||||
<p xml:lang="ka">უახლეს ვერსიაში აპის გაშვებისას თქვენ მიღებთ მისალმების გვერდს, რომელიც საშუალებას გაძლევთ, აირჩიოთ ანგარიში, რომლის გამოყენებაც გსურთ და საშუალებას მოგცემთ, სხვა ანგარიშებში შეხვიდეთ. მისალმების ეკრანი ასევე გაგაფრთხილებთ, როცა NeoChat-ს ანგარიშის ჩატვირთვა არ შეეძლება.</p>
|
||||
<p xml:lang="pt-BR">Na versão mais recente, ao iniciar o aplicativo, você verá uma página de boas-vindas que permite escolher qual conta deseja usar e fazer login em outras contas. A tela de boas-vindas também avisará quando o NeoChat não conseguir carregar uma conta.</p>
|
||||
<p xml:lang="ru">В новой версии при запуске приложения открывается страница приветствия, на которой можно выбрать учётную запись для входа или добавить другие учётные записи. Также на странице приветствия отображается предупреждение, если NeoChat не удалось загрузить учётную запись.</p>
|
||||
<p xml:lang="sl">V najnovejši različici boste ob zagonu aplikacije prejeli pozdravno stran, kjer lahko izberete, kateri račun želite uporabiti, in se prijavite v druge račune. Pozdravna stran vas bo opozorila tudi, ko NeoChat ne bo mogel naložiti računa.</p>
|
||||
<p>NeoChat will also let you register a new account directly from the app itself. Deactivating your Matrix account is also possible from within NeoChat.</p>
|
||||
<p xml:lang="ar">يتيح نيوتشات أيضاً تسجيل حساب جديد مباشرة من التطبيق نفسه. كما يمكن تعطيل حساب ماتركس من داخل نيوتشات.</p>
|
||||
<p xml:lang="ca">El NeoChat també us permetrà registrar un compte nou directament des de l'aplicació mateixa. També és possible desactivar el vostre compte de Matrix des del NeoChat.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat també us permetrà registrar un compte nou directament des de l'aplicació mateixa. També és possible desactivar el vostre compte de Matrix des de NeoChat.</p>
|
||||
<p xml:lang="ka">NeoChat ასევე საშუალებას მოგცემთ, დაარეგისტრიროთ ახალი ანგარიში პირდაპირ აპიდან. NeoChat-იდან ასევე შესაძლებელია თქვენი მატრიცის ანგარიშის დეაქტივაციაც.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat também permite que você registre uma nova conta diretamente pelo aplicativo. Desativar sua conta Matrix também é possível a partir do NeoChat.</p>
|
||||
<p xml:lang="ru">В NeoChat также можно зарегистрировать новую учётную запись Matrix. Учётную запись Matrix также можно деактивировать непосредственно в NeoChat.</p>
|
||||
<p xml:lang="sl">NeoChat vam omogoča tudi registracijo novega računa neposredno iz same aplikacije. Deaktivacija računa Matrix je mogoča tudi znotraj NeoChata.</p>
|
||||
<p>Spaces are a relatively new feature of Matrix that let you group chat channels together. This is used to improve the discoverability of rooms, manage large communities, or just tidy all the channels you are in. You can now do all this without leaving NeoChat.</p>
|
||||
<p xml:lang="ar">الفضاءات هي ميزة جديدة نسبياً في ماتركس تسمح بتجميع قنوات الدردشة معاً. يُستخدم هذا لتحسين قابلية اكتشاف الغرف، أو إدارة المجتمعات الكبيرة، أو مجرد ترتيب القنوات المشترك فيها. يمكن القيام بكل ذلك الآن دون مغادرة نيوتشات.</p>
|
||||
<p xml:lang="ca">Els espais són una característica relativament nova de Matrix que us permet agrupar canals de xat junts. Això s'utilitza per a millorar la descoberta de sales, gestionar comunitats grans, o simplement ordenar tots els canals en els quals esteu. Ara podeu fer tot això sense sortir del NeoChat.</p>
|
||||
<p xml:lang="ca-valencia">Els espais són una característica relativament nova de Matrix que us permet agrupar canals de xat junts. Açò s'utilitza per a millorar la descoberta de sales, gestionar comunitats grans, o senzillament ordenar tots els canals en els quals esteu. Ara podeu fer tot açò sense eixir de NeoChat.</p>
|
||||
<p xml:lang="ka">სივრცეები მატრიცის, შედარებით, ახალი ფუნქციაა, რომელიც ჩატის არხების დაჯგუფების საშუალებას გაძლევთ. ის გამოიყენება ოთახების აღმოჩენადობისთვის, დიდი საზოგადოებების სამართავად და ზოგადად არხების ერთად შესაკრებად. ამისი გაკეთება NeoChat-იდან გაუსვლელად შეგიძლიათ.</p>
|
||||
<p xml:lang="pt-BR">Os Espaços são um recurso relativamente novo do Matrix que permite agrupar canais de bate-papo. Isso é usado para melhorar a visibilidade das salas, gerenciar grandes comunidades ou simplesmente organizar todos os canais dos quais você participa. Agora você pode fazer tudo isso sem sair do NeoChat.</p>
|
||||
<p xml:lang="ru">Пространства — это относительно новая функция Matrix, позволяющая группировать каналы чатов. Они используются для упрощения поиска комнат, управления большими сообществами или просто для упорядочивания всех ваших каналов. Теперь это можно делать, не покидая NeoChat.</p>
|
||||
<p xml:lang="sl">Prostori so relativno nova funkcija Matrixa, ki omogoča združevanje klepetalnih kanalov. To se uporablja za izboljšanje vidnosti sob, upravljanje velikih skupnosti ali preprosto urejanje vseh kanalov, v katerih ste. Zdaj lahko vse to storite, ne da bi zapustili NeoChat.</p>
|
||||
<p>NeoChat won't let you miss any new notifications anymore. We added a new page that includes all your recent notifications and when NeoChat is closed, you will still be able to receive push notifications. The main timeline will let you know when more messages are loading and when you reach the end of it.</p>
|
||||
<p xml:lang="ar">لن يفوّت نيوتشات أي تنبيهات جديدة بعد الآن. أُضيفت صفحة جديدة تتضمن كافة التنبيهات الأخيرة، وعند إغلاق نيوتشات، ستظل القدرة على استقبال التنبيهات الدفعية قائمة. يُعلِمك الخط الزمني الرئيسي عند تحميل مزيد من الرسائل وعند الوصول إلى نهايته.</p>
|
||||
<p xml:lang="ca">El NeoChat ja no deixarà que us perdeu cap notificació nova. Hem afegit una pàgina nova que inclou totes les notificacions recents i quan el NeoChat estigui tancat, encara podreu rebre notificacions automàtiques. La línia de temps principal us avisarà quan s'estiguin carregant més missatges i quan arribeu al seu final.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat ja no deixarà que vos perdeu cap notificació nova. Hem afegit una pàgina nova que inclou totes les notificacions recents i quan NeoChat estiga tancat, encara podreu rebre notificacions automàtiques. La línia de temps principal vos avisarà quan s'estiguen carregant més missatges i quan arribeu fins al seu final.</p>
|
||||
<p xml:lang="ka">NeoChat აღარ მოგცემთ საშუალებას, ახალი შეტყობინებები გამოტოვოთ. ჩვენ დავამატეთ ახალი გვერდი, რომელიც თქვენს უახლეს გაფრთხილებებს შეიცავს და როცა NeoChat დახურულია, თქვენ მაინც შეძლებთ, პუშ-გაფრთხილებები მიიღოთ. მთავარი დროის ხაზი საშუალებას მოგცემთ, გაიგოთ, როდის მოხდება მეტი შეტყობინების ჩატვირთვა და როცა ბოლოში გახვალთ.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat não deixará você perder mais nenhuma notificação. Adicionamos uma nova página que inclui todas as suas notificações recentes e, mesmo com o NeoChat fechado, você ainda poderá receber notificações push. A linha do tempo principal mostrará quando novas mensagens estiverem sendo carregadas e quando você chegar ao final.</p>
|
||||
<p xml:lang="ru">Вы более не пропустите ни одного нового уведомления NeoChat. Добавлена страница со всеми последними уведомлениями, а при закрытии приложения будут приходить push-уведомления. Основная лента сообщений теперь показывает процесс загрузки новых сообщений и её окончание.</p>
|
||||
<p xml:lang="sl">Z NeoChatom ne boste več zamudili nobenega novega obvestila. Dodali smo novo stran, ki vključuje vsa vaša nedavna obvestila, in ko je NeoChat zaprt, boste še vedno lahko prejemali potisna obvestila. Glavna časovnica vas bo obvestila, kdaj se nalagajo nova sporočila in kdaj pridete do konca.</p>
|
||||
<p>More NeoChat Goodies</p>
|
||||
<p xml:lang="ar">مزايا إضافية في نيوتشات</p>
|
||||
<p xml:lang="ca">Més millores del NeoChat</p>
|
||||
<p xml:lang="ca-valencia">Més millores de NeoChat</p>
|
||||
<p xml:lang="ka">NeoChat-ის მეტი სიკეთე</p>
|
||||
<p xml:lang="pt-BR">Mais novidades do NeoChat</p>
|
||||
<p xml:lang="ru">Дополнительные возможности NeoChat</p>
|
||||
<p xml:lang="sl">Več dobrot NeoChata</p>
|
||||
<ul>
|
||||
<li>QR Codes to share contacts</li>
|
||||
<li xml:lang="ar">رموز استجابة سريعة (QR) لمشاركة جهات الاتصال</li>
|
||||
<li xml:lang="ca">Codis QR per a compartir contactes</li>
|
||||
<li xml:lang="ca-valencia">Codis QR per a compartir contactes</li>
|
||||
<li xml:lang="es">Códigos QR para compartir contactos</li>
|
||||
<li xml:lang="ka">QR კოდები კონტაქტების გასაზიარებლად</li>
|
||||
<li xml:lang="pt-BR">Códigos QR para compartilhar contatos</li>
|
||||
<li xml:lang="ru">QR-коды для обмена контактами;</li>
|
||||
<li xml:lang="sl">QR kode za deljenje stikov</li>
|
||||
<li>Improved room upgrades</li>
|
||||
<li xml:lang="ar">ترقيات محسّنة للغرف</li>
|
||||
<li xml:lang="ca">Actualitzacions millorades de les sales</li>
|
||||
<li xml:lang="ca-valencia">Actualitzacions millorades de les sales</li>
|
||||
<li xml:lang="ka">გაუმჯობესდა ოთახის განახლებები</li>
|
||||
<li xml:lang="pt-BR">Melhorias nas salas</li>
|
||||
<li xml:lang="ru">Улучшены процедуры обновления комнат;</li>
|
||||
<li xml:lang="sl">Izboljšane nadgradnje sob</li>
|
||||
<li>Added button to reject invitation and ignore user</li>
|
||||
<li xml:lang="ar">أُضيف زر لرفض الدعوة وتجاهل المستخدم</li>
|
||||
<li xml:lang="ca">S'ha afegit un botó per a rebutjar una invitació i ignorar l'usuari</li>
|
||||
<li xml:lang="ca-valencia">S'ha afegit un botó per a rebutjar una invitació i ignorar l'usuari</li>
|
||||
<li xml:lang="ka">დაემატა ღილაკი მოსაწვევის უარყოფისთვის და მომხმარებლის დასაიგნორებლად</li>
|
||||
<li xml:lang="pt-BR">Adicionado botão para rejeitar convite e ignorar usuário</li>
|
||||
<li xml:lang="ru">Добавлена кнопка для отклонения приглашения и игнорирования пользователя;</li>
|
||||
<li xml:lang="sl">Dodan gumb za zavrnitev povabila in ignoriranje uporabnika</li>
|
||||
<li>Display device security details</li>
|
||||
<li xml:lang="ar">عرض تفاصيل أمان الجهاز</li>
|
||||
<li xml:lang="ca">Mostra els detalls de seguretat del dispositiu</li>
|
||||
<li xml:lang="ca-valencia">Mostra els detalls de seguretat del dispositiu</li>
|
||||
<li xml:lang="es">Mostrar detalles de la seguridad del dispositivo</li>
|
||||
<li xml:lang="ka">მოწყობილობის უსაფრთხოების დეტალების ჩვენება</li>
|
||||
<li xml:lang="pt-BR">Exibir detalhes de segurança do dispositivo</li>
|
||||
<li xml:lang="ru">Просмотр подробных сведений о безопасности устройства;</li>
|
||||
<li xml:lang="sl">Prikaži varnostne podrobnosti naprave</li>
|
||||
<li>Added room security settings</li>
|
||||
<li xml:lang="ar">أُضيفت إعدادات أمان الغرفة</li>
|
||||
<li xml:lang="ca">S'ha afegit la configuració de seguretat de la sala</li>
|
||||
<li xml:lang="ca-valencia">S'ha afegit la configuració de seguretat de la sala</li>
|
||||
<li xml:lang="es">Se han añadido ajustes de la seguridad de las salas</li>
|
||||
<li xml:lang="ka">დაემატა ოთახის უსაფრთხოების პარამეტრები</li>
|
||||
<li xml:lang="pt-BR">Adicionadas configurações de segurança da sala</li>
|
||||
<li xml:lang="ru">Добавлены параметры безопасности комнат;</li>
|
||||
<li xml:lang="sl">Dodane varnostne nastavitve sobe</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
@@ -611,13 +534,6 @@
|
||||
<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>
|
||||
<p xml:lang="ar">بعيداً عن التجديد البصري، يمكن لنيوتشات الآن عرض أحداث الموقع وكذلك خريطة بمواقع جميع المستخدمين الذين يبثون مواقعهم حالياً باستخدام تكامل ماتركس مع Itineray. يعد هذا رائعاً لتحديد أماكن الأصدقاء.</p>
|
||||
<p xml:lang="ca">A part d'una revisió visual, ara el NeoChat pot mostrar esdeveniments d'ubicació i també un mapa amb la ubicació de tots els usuaris que actualment emeten la seva ubicació utilitzant la integració de Matrix de l'Itineray. És genial per a localitzar on són els vostres amics.</p>
|
||||
<p xml:lang="ca-valencia">A part d'una revisió visual, ara NeoChat pot mostrar esdeveniments d'ubicació i també un mapa amb la ubicació de tots els usuaris que actualment emeten la seua ubicació utilitzant la integració de Matrix de l'Itineray. És genial per a localitzar on són els vostres amics.</p>
|
||||
<p xml:lang="ka">ვიზუალური მხრის განახლებასთან ერთად NeoChat-ს ახლა შეუძლია მდებარეობის მოვლენების ჩვენება და ასევე შეუძლია ყველა მომხმარებელი, რომელიც გადმოსცემს თავის მდებარეობას, Itinerary-ის მატრიცის ინტეგრაციით გაჩვენოთ. ეს კარგია იმისთვის, რომ გაიგოთ, სად არიან თქვენი მეგობრები.</p>
|
||||
<p xml:lang="pt-BR">Além de uma reformulação visual, o NeoChat agora pode exibir eventos de localização e também um mapa com a localização de todos os usuários que estão transmitindo sua localização usando a integração Matrix do Itineray. Ótimo para localizar seus amigos.</p>
|
||||
<p xml:lang="ru">Помимо визуального обновления, в NeoChat добавлена возможность отображения событий местоположения, а также карты с местоположением всех пользователей, которые в данный момент транслируют свои геоданные через интеграцию с Itineray в Matrix. Это удобно для того, чтобы узнать, где находятся ваши друзья.</p>
|
||||
<p xml:lang="sl">Poleg vizualne prenove lahko NeoChat zdaj prikazuje lokacijske dogodke in tudi zemljevid z lokacijo vseh uporabnikov, ki trenutno oddajajo svojo lokacijo, z uporabo integracije Itineray Matrix. Odlično za iskanje lokacij vaših prijateljev.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="23.04.3" date="2023-07-06"/>
|
||||
@@ -627,29 +543,8 @@
|
||||
<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 xml:lang="ar">يُحسّن نيوتشات تصميمه بتعديلات توفر مخططاً أكثر ضغطاً وقائمة أبسط تعمل بشكل أفضل مع قائمة الغرف المطوية.</p>
|
||||
<p xml:lang="ca">El NeoChat millora el seu disseny amb retocs que proporcionen una disposició més compacta i un menú més senzill que funciona millor per a la llista reduïda de sales.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat millora el seu disseny amb retocs que proporcionen una disposició més compacta i un menú més senzill que funciona millor per a la llista reduïda de sales.</p>
|
||||
<p xml:lang="ka">NeoChat-მა გააუმჯობესა თავისი დიზაინი ცვლილებებით, რომლებიც გაწვდით უფრო კომპაქტური განლაგებას და გამარტივებულ მენიუს, რომელიც უკეთ მუშაობს აკეცილი ოთახების სიისთვის.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat aprimorou seu design com ajustes que proporcionam um layout mais compacto e um menu mais simples, que funciona melhor para a lista de salas recolhida.</p>
|
||||
<p xml:lang="ru">В оформлении NeoChat реализованы изменения для более компактного расположения элементов и упрощённого меню, которое лучше работает со свёрнутым списком комнат.</p>
|
||||
<p xml:lang="sl">NeoChat izboljšuje svojo zasnovo s prilagoditvami, ki zagotavljajo bolj kompaktno postavitev in enostavnejši meni, ki bolje deluje za strnjen seznam sob.</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 xml:lang="ar">حُسّنت أيضاً عناصر التحكم في الفيديو، وأُضيف أمر جديد /knock <room-id> لإرسال حدث طرق إلى غرفة، ويمكن الآن تحرير رسالة سابقة في السطر نفسه داخل لوحة الدردشة.</p>
|
||||
<p xml:lang="ca">També hem millorat els controls de vídeo, s'ha afegit una nova ordre /knock <id-sala> per a enviar un esdeveniment «knock» a una sala, i ara podeu editar un missatge anterior inclòs, dins de la subfinestra de xat.</p>
|
||||
<p xml:lang="ca-valencia">També hem millorat els controls de vídeo, s'ha afegit una nova ordre /knock <id-sala> per a enviar un esdeveniment «knock» a una sala, i ara podeu editar un missatge anterior inclòs, dins de la subfinestra de xat.</p>
|
||||
<p xml:lang="ka">ჩვენ ასევე გავაუმჯობესეთ ვიდეოს მართვა, დავამატეთ ახალი ბრძანება /knock <ოთახის-id> ოთახში დაკაკუნების მოვლენის გასაგზავნად და ასევე შეგიძლიათ, წინა შეტყობინება ხაზშივე, ჩატის პანელში ჩაასწოროთ.</p>
|
||||
<p xml:lang="pt-BR">Também melhoramos os controles de vídeo, adicionamos um novo comando /knock <room-id> para enviar um evento knock para uma sala, e agora você pode editar uma mensagem anterior em linha, no painel de bate-papo.</p>
|
||||
<p xml:lang="ru">Также улучшены элементы управления видео, добавлена новая команда /knock <room-id> для отправки события knock в комнату, а также реализовано редактирование предыдущих сообщений непосредственно в области чата.</p>
|
||||
<p xml:lang="sl">Izboljšali smo tudi video kontrole, dodali nov ukaz /knock <room-id> za pošiljanje dogodka knock v sobo, zdaj pa lahko urejate prejšnje sporočilo v vrstici, v podoknu za klepet.</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>
|
||||
<p xml:lang="ar">تتضمن تحسينات سهولة الاستخدام الأخرى تجديد التنقل عبر لوحة المفاتيح واختصارات مثل Ctrl+PgUp/PgDn التي تسمح بالانتقال السريع بين الغرف.</p>
|
||||
<p xml:lang="ca">Altres millores d'usabilitat inclouen una revisió de la navegació amb el teclat i dreceres com Ctrl+Re Pàg/Av Pàg que us permeten saltar de sala en sala.</p>
|
||||
<p xml:lang="ca-valencia">Altres millores d'usabilitat inclouen una revisió de la navegació amb el teclat i dreceres com Ctrl+«Re Pàg»/«Av Pàg» que vos permeten saltar de sala en sala.</p>
|
||||
<p xml:lang="ka">სხვა გამოყენებადობის გაუმჯობესებები შეიცავს კლავიატურის ნავიგაციის გადაკეთებას და ისეთ მალსახმობებს, როგორიცაა Ctrl+PgUp/PgDn, რომელიც ოთახიდან ოთახზე გადასვლის საშუალებას გაძლევთ.</p>
|
||||
<p xml:lang="pt-BR">Outras melhorias de usabilidade incluem uma reformulação da navegação pelo teclado e atalhos como Ctrl+PgUp/PgDn que permitem que você pule de sala em sala.</p>
|
||||
<p xml:lang="ru">К другим улучшениям в удобстве использования относится переработанная навигация с клавиатурой и комбинации клавиш, такие как Ctrl+PgUp/PgDn, для быстрого перехода между комнатами.</p>
|
||||
<p xml:lang="sl">Druge izboljšave uporabnosti vključujejo prenovo navigacije s tipkovnico in bližnjice, kot sta Ctrl+PgUp/PgDn, ki omogočajo preskakovanje med sobami.</p>
|
||||
</description>
|
||||
<artifacts>
|
||||
<artifact type="binary" platform="x86_64-windows-msvc">
|
||||
@@ -663,69 +558,13 @@
|
||||
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
|
||||
<description>
|
||||
<p>New features and bugfixes:</p>
|
||||
<p xml:lang="ar">ميزات جديدة وإصلاحات للعلل:</p>
|
||||
<p xml:lang="ca">Característiques noves i correccions d'errors:</p>
|
||||
<p xml:lang="ca-valencia">Característiques noves i esmenes d'errors:</p>
|
||||
<p xml:lang="es">Nuevas funciones y corrección de fallos:</p>
|
||||
<p xml:lang="ka">ახალი ფუნქციები და შეცდომების გასწორებები:</p>
|
||||
<p xml:lang="pt-BR">Novas funcionalidades e correções de bugs:</p>
|
||||
<p xml:lang="ru">Новые возможности и исправления ошибок:</p>
|
||||
<p xml:lang="sl">Nove zmožnosti in popravki napak:</p>
|
||||
<ul>
|
||||
<li>Notifications will now be shown for all accounts, not just the active one</li>
|
||||
<li xml:lang="ar">تُعرض التنبيهات الآن لكافة الحسابات، وليس فقط الحساب النشط</li>
|
||||
<li xml:lang="ca">Ara es mostraran les notificacions per a tots els comptes, no només l'actiu</li>
|
||||
<li xml:lang="ca-valencia">Ara es mostraran les notificacions per a tots els comptes, no només l'actiu</li>
|
||||
<li xml:lang="es">Se muestran notificaciones de todas las cuentas, no solo de la activa</li>
|
||||
<li xml:lang="ka">გაფრთხილებები ნაჩვენები იქნება ყველა ანგარიშისთვის და არა, მხოლოდ, აქტიურისთვის</li>
|
||||
<li xml:lang="pt-BR">Agora, as notificações serão exibidas para todas as contas, não apenas para a conta ativa</li>
|
||||
<li xml:lang="ru">Уведомления будут выводиться для всех учётных записей, а не только для активной;</li>
|
||||
<li xml:lang="sl">Obvestila bodo zdaj prikazana za vse račune, ne le za aktivnega</li>
|
||||
<li>There is a new "compact" mode for the room list</li>
|
||||
<li xml:lang="ar">يوجد وضع "مضغوط" جديد لقائمة الغرف</li>
|
||||
<li xml:lang="ca">Hi ha un mode «compacte» nou per a la llista de sales</li>
|
||||
<li xml:lang="ca-valencia">Hi ha un mode «compacte» nou per a la llista de sales</li>
|
||||
<li xml:lang="es">Nuevo modo «compacto» para la lista de salas</li>
|
||||
<li xml:lang="ka">ოთახების სიისთვის არსებობს ახალი "კომპაქტური" რეჟიმი</li>
|
||||
<li xml:lang="pt-BR">Existe um novo modo "compacto" para a lista de salas</li>
|
||||
<li xml:lang="ru">Добавлен новый компактный режим списка комнат;</li>
|
||||
<li xml:lang="sl">Za seznam sob je na voljo nov "kompaktni" način.</li>
|
||||
<li>You can now search in the room history</li>
|
||||
<li xml:lang="ar">يمكن البحث الآن في تاريخ الغرفة</li>
|
||||
<li xml:lang="ca">Ara podeu cercar a l'historial de sales</li>
|
||||
<li xml:lang="ca-valencia">Ara podeu buscar en l'historial de sales</li>
|
||||
<li xml:lang="es">Ahora se puede buscar en el historial de salas</li>
|
||||
<li xml:lang="ka">ახლა ოთახის ისტორიაში ძებნა შეგიძლიათ</li>
|
||||
<li xml:lang="pt-BR">Agora você pode pesquisar no histórico da sala</li>
|
||||
<li xml:lang="ru">Добавлен поиск по истории комнат;</li>
|
||||
<li xml:lang="sl">Zdaj lahko iščete po zgodovini sobe</li>
|
||||
<li>Emojis and Reactions have been significantly improved</li>
|
||||
<li xml:lang="ar">حُسّنت الرموز التعبيرية والتفاعلات بشكل ملحوظ</li>
|
||||
<li xml:lang="ca">Els emojis i les reaccions s'han millorat significativament</li>
|
||||
<li xml:lang="ca-valencia">Els emoji i les reaccions s'han millorat significativament</li>
|
||||
<li xml:lang="es">Los emojis y las reacciones se han mejorado significativamente</li>
|
||||
<li xml:lang="ka">ემოჯიები და რეაქციები საგრძნობლად გაუმჯობესდა</li>
|
||||
<li xml:lang="pt-BR">Os emojis e as reações foram significativamente aprimorados</li>
|
||||
<li xml:lang="ru">Эмодзи и реакции были значительно улучшены;</li>
|
||||
<li xml:lang="sl">Čustvenčki in reakcije so bili znatno izboljšani</li>
|
||||
<li>Fixed several crashes around user invitations</li>
|
||||
<li xml:lang="ar">أُصلحت عدة انهيارات تتعلق بدعوات المستخدمين</li>
|
||||
<li xml:lang="ca">S'han corregit diverses fallades respecte les invitacions d'usuari</li>
|
||||
<li xml:lang="ca-valencia">S'han corregit diverses fallades respecte les invitacions d'usuari</li>
|
||||
<li xml:lang="es">Se han corregido varios fallos relacionados con las invitaciones de los usuarios</li>
|
||||
<li xml:lang="ka">გასწორდა რამდენიმე შეცდომა მომხმარებლის მოწვევის ფუნქციის გარშემო</li>
|
||||
<li xml:lang="pt-BR">Corrigidas várias falhas relacionadas a convites de usuários</li>
|
||||
<li xml:lang="ru">Исправлено несколько аварийных завершений, связанных с приглашениями пользователей;</li>
|
||||
<li xml:lang="sl">Odpravljenih je bilo več sesutij pri povabilih uporabnikov</li>
|
||||
<li>Room permission settings can now be configured</li>
|
||||
<li xml:lang="ar">يمكن ضبط إعدادات أذونات الغرفة الآن</li>
|
||||
<li xml:lang="ca">Ara es pot configurar les opcions dels permisos de sala</li>
|
||||
<li xml:lang="ca-valencia">Ara es pot configurar les opcions dels permisos de sala</li>
|
||||
<li xml:lang="es">Ahora se pueden configurar los ajustes de los permisos de las salas</li>
|
||||
<li xml:lang="ka">ახლა შეგიძლიათ ოთახზე წვდომების მორგება</li>
|
||||
<li xml:lang="pt-BR">Agora é possível configurar as permissões da sala</li>
|
||||
<li xml:lang="ru">Добавлена возможность настраивать разрешения для комнат;</li>
|
||||
<li xml:lang="sl">Nastavitve dovoljenj za sobo je zdaj mogoče konfigurirati</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
@@ -739,88 +578,16 @@
|
||||
<url>https://www.plasma-mobile.org/2022/06/28/plasma-mobile-gear-22-06/</url>
|
||||
<description>
|
||||
<p>This release brings you various small bugfixes and improvements:</p>
|
||||
<p xml:lang="ar">يوفر هذا الإصدار إصلاحات وتحسينات متنوعة صغيرة:</p>
|
||||
<p xml:lang="ca">Aquesta versió us ofereix diverses correccions d'errors i millores petites:</p>
|
||||
<p xml:lang="ca-valencia">Esta versió vos oferix diverses esmenes d'errors i millores xicotetes:</p>
|
||||
<p xml:lang="es">Esta versión proporciona diversas mejoras menores y correcciones de fallos:</p>
|
||||
<p xml:lang="ka">ეს ვერსია შეიცავს რამდენიმე პატარა შეცდომის გასწორებას და გაუმჯობესებას:</p>
|
||||
<p xml:lang="pt-BR">Esta versão traz diversas pequenas correções de bugs e melhorias:</p>
|
||||
<p xml:lang="ru">В этом выпуске исправлены различные ошибки и внесены улучшения:</p>
|
||||
<p xml:lang="sl">Ta izdaja vam prinaša različne manjše popravke napak in izboljšave:</p>
|
||||
<ul>
|
||||
<li>Sending of typing notifications can now be disabled.</li>
|
||||
<li xml:lang="ar">يمكن تعطيل إرسال تنبيهات الكتابة الآن.</li>
|
||||
<li xml:lang="ca">Ara es pot desactivar l'enviament de notificacions d'escriptura.</li>
|
||||
<li xml:lang="ca-valencia">Ara es pot desactivar l'enviament de notificacions d'escriptura.</li>
|
||||
<li xml:lang="ka">ახლა შეგიძლიათ, კრეფის შესახებ გაფრთხილება გამორთოთ.</li>
|
||||
<li xml:lang="pt-BR">Agora é possível desativar o envio de notificações de digitação.</li>
|
||||
<li xml:lang="ru">Отправку уведомлений о наборе текста теперь можно отключить;</li>
|
||||
<li xml:lang="sl">Pošiljanje obvestil o tipkanju je zdaj mogoče onemogočiti.</li>
|
||||
<li>In the room list, the scrollbar will now disappear correctly when it is not needed.</li>
|
||||
<li xml:lang="ar">في قائمة الغرف، يختفي شريط التمرير الآن بشكل صحيح عند عدم الحاجة إليه.</li>
|
||||
<li xml:lang="ca">A la llista de sales, la barra de desplaçament ara desapareixerà correctament quan no es necessiti.</li>
|
||||
<li xml:lang="ca-valencia">En la llista de sales, la barra de desplaçament ara desapareixerà correctament quan no es necessite.</li>
|
||||
<li xml:lang="ka">ოთახების სიაში ჩოჩია ახლა სწორად ქრება, როცა ის საჭირო არაა.</li>
|
||||
<li xml:lang="pt-BR">Na lista de salas, a barra de rolagem agora desaparecerá corretamente quando não for necessária.</li>
|
||||
<li xml:lang="ru">В списке комнат полоса прокрутки теперь скрывается, если не нужна;</li>
|
||||
<li xml:lang="sl">Na seznamu sob bo drsnik zdaj pravilno izginil, ko ga ne boste potrebovali.</li>
|
||||
<li>On wayland, NeoChat will now raise correctly when clicking on a notification.</li>
|
||||
<li xml:lang="ar">في ويلاند، يبرز نيوتشات الآن بشكل صحيح عند النقر على التنبيه.</li>
|
||||
<li xml:lang="ca">Al Wayland, ara el NeoChat elevarà correctament en fer clic a una notificació.</li>
|
||||
<li xml:lang="ca-valencia">A Wayland, ara NeoChat elevarà correctament quan es clique damunt d'una notificació.</li>
|
||||
<li xml:lang="ka">Wayland-ზე NeoChat ახლა სწორად ამოიწევა, როცა გაფრთხილებაზე დააწკაპუნებთ.</li>
|
||||
<li xml:lang="pt-BR">No Wayland, o NeoChat agora será exibido corretamente ao clicar em uma notificação.</li>
|
||||
<li xml:lang="ru">В сеансах Wayland NeoChat теперь активируется при щелчке по уведомлению;</li>
|
||||
<li xml:lang="sl">Na Waylandu se NeoChat zdaj pravilno sproži ob kliku na obvestilo.</li>
|
||||
<li>Several bugs have been fixed that would sometimes cause messages containing markdown and/or HTML elements to be sent incorrectly.</li>
|
||||
<li xml:lang="ar">أُصلحت عدة علل كانت تتسبب أحياناً في إرسال الرسائل التي تحتوي على عناصر markdown أو HTML بشكل غير صحيح.</li>
|
||||
<li xml:lang="ca">S'han corregit diversos errors que de vegades feien que els missatges que contenien elements de Markdown i/o HTML s'enviessin incorrectament.</li>
|
||||
<li xml:lang="ca-valencia">S'han corregit diversos errors que de vegades feien que els missatges que contenien elements de Markdown i/o HTML s'enviaren incorrectament.</li>
|
||||
<li xml:lang="ka">გასწორდა რამდენიმე შეცდომა, რომლებიც ხანდახან შეტყობინებებს, რომლებიც markdown-ს, ან/და HTML ელემენტებს შეიცავენ, არასწორად აგზავნიდნენ.</li>
|
||||
<li xml:lang="pt-BR">Diversos erros foram corrigidos, os quais, por vezes, causavam o envio incorreto de mensagens contendo elementos Markdown e/ou HTML.</li>
|
||||
<li xml:lang="ru">Исправлены ошибки, из-за которых сообщения, содержащие элементы разметки Markdown и/или HTML, иногда отправлялись некорректно;</li>
|
||||
<li xml:lang="sl">Odpravljenih je bilo več hroščev, zaradi katerih so bila sporočila, ki so vsebovala elemente Markdown in/ali HTML, včasih napačno poslana.</li>
|
||||
<li>The quick switcher can now be controlled using the mouse.</li>
|
||||
<li xml:lang="ar">يمكن التحكم في المبدل السريع باستخدام الفأرة الآن.</li>
|
||||
<li xml:lang="ca">El commutador ràpid ara es pot controlar amb el ratolí.</li>
|
||||
<li xml:lang="ca-valencia">El commutador ràpid ara es pot controlar amb el ratolí.</li>
|
||||
<li xml:lang="ka">სწრაფი გადამრთველის მართვა ახლა თაგუნათი შეგიძლიათ.</li>
|
||||
<li xml:lang="pt-BR">O seletor rápido agora pode ser controlado usando o mouse.</li>
|
||||
<li xml:lang="ru">Для быстрого переключения теперь возможно использовать мышь;</li>
|
||||
<li xml:lang="sl">Hitri preklopnik je zdaj mogoče upravljati z miško.</li>
|
||||
<li>There is now an option to disable automatic room sidebar opening when resizing the window.</li>
|
||||
<li xml:lang="ar">يتوفر الآن خيار لتعطيل الفتح الآلي للشريط الجانبي للغرفة عند تغيير حجم النافذة.</li>
|
||||
<li xml:lang="ca">Ara hi ha una opció per a desactivar l'obertura automàtica de la barra lateral de la sala en canviar la mida de la finestra.</li>
|
||||
<li xml:lang="ca-valencia">Ara hi ha una opció per a desactivar l'obertura automàtica de la barra lateral de la sala en canviar la mida de la finestra.</li>
|
||||
<li xml:lang="ka">ახლა გაქვთ არჩევანი, რომ გამორთოთ ავტომატური ოთახის გვერდითი პანელის გახსნა ფანჯრის ზომის შეცვლისას.</li>
|
||||
<li xml:lang="pt-BR">Agora existe uma opção para desativar a abertura automática da barra lateral da sala ao redimensionar a janela.</li>
|
||||
<li xml:lang="ru">Добавлена возможность отключить автоматическое открытие боковой панели комнат при изменении размера окна;</li>
|
||||
<li xml:lang="sl">Zdaj je na voljo možnost onemogočanja samodejnega odpiranja stranske vrstice sobe pri spreminjanju velikosti okna.</li>
|
||||
<li>Creation of custom emojis has been fixed.</li>
|
||||
<li xml:lang="ar">أُصلح إنشاء الرموز التعبيرية المخصصة.</li>
|
||||
<li xml:lang="ca">S'ha corregit la creació d'emojis personalitzats.</li>
|
||||
<li xml:lang="ca-valencia">S'ha corregit la creació d'emoji personalitzats.</li>
|
||||
<li xml:lang="es">Se ha corregido la creación de emojis personalizados.</li>
|
||||
<li xml:lang="ka">გასწორდა მომხმარებლის ემოჯიების შექმნა.</li>
|
||||
<li xml:lang="pt-BR">A criação de emojis personalizados foi corrigida.</li>
|
||||
<li xml:lang="ru">Исправлено создание пользовательских эмодзи;</li>
|
||||
<li xml:lang="sl">Ustvarjanje čustvenčkov po meri je bilo popravljeno.</li>
|
||||
<li>Editing or replying to the last message using the keyboard shortcuts now works correctly.</li>
|
||||
<li xml:lang="ar">يعمل تحرير الرسالة الأخيرة أو الرد عليها باستخدام اختصارات لوحة المفاتيح بشكل صحيح الآن.</li>
|
||||
<li xml:lang="ca">L'edició o la resposta a l'últim missatge utilitzant les dreceres de teclat ara funciona correctament.</li>
|
||||
<li xml:lang="ca-valencia">L'edició o la resposta a l'últim missatge utilitzant les dreceres de teclat ara funciona correctament.</li>
|
||||
<li xml:lang="ka">ბოლო შეტყობინების ჩასწორება და მასზე პასუხი კლავიატურის მალსახმობებით ახლა სწორად მუშაობს.</li>
|
||||
<li xml:lang="pt-BR">Agora, editar ou responder à última mensagem usando os atalhos de teclado funciona corretamente.</li>
|
||||
<li xml:lang="ru">Исправлена работа комбинаций клавиш для редактирования или ответа на последнее сообщение.</li>
|
||||
<li xml:lang="sl">Urejanje ali odgovarjanje na zadnje sporočilo z uporabo bližnjic na tipkovnici zdaj deluje pravilno.</li>
|
||||
<li>When switching between rooms using the keyboard, the switching direction is now correct.</li>
|
||||
<li xml:lang="ar">عند التبديل بين الغرف باستخدام لوحة المفاتيح، يكون اتجاه التبديل صحيحاً الآن.</li>
|
||||
<li xml:lang="ca">Quan es canvia entre sales utilitzant el teclat, la direcció de commutació ara és correcta.</li>
|
||||
<li xml:lang="ca-valencia">Quan es canvia entre sales utilitzant el teclat, la direcció de commutació ara és correcta.</li>
|
||||
<li xml:lang="ka">ოთახებს შორის კლავიატურით გადართვისას გადართვის მიმართულება ახლა სწორია.</li>
|
||||
<li xml:lang="pt-BR">Ao alternar entre salas usando o teclado, a direção da alternância agora está correta.</li>
|
||||
<li xml:lang="ru">При переключении между комнатами с клавиатуры направление переключения теперь корректное;</li>
|
||||
<li xml:lang="sl">Pri preklapljanju med sobami s tipkovnico je smer preklapljanja zdaj pravilna.</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
@@ -828,76 +595,18 @@
|
||||
<url>https://www.plasma-mobile.org/2022/04/26/plasma-mobile-gear-22-04/</url>
|
||||
<description>
|
||||
<p>NeoChat now lets you filter and enter a room directly from KRunner (Plasma Search). Aside from that there is also various bug fixes regarding the typing notifications.</p>
|
||||
<p xml:lang="ar">يتيح نيوتشات الآن ترشيح ودخول الغرفة مباشرة من KRunner (بحث بلازما). وبالإضافة إلى ذلك، تتوفر إصلاحات متنوعة للعلل المتعلقة بتنبيهات الكتابة.</p>
|
||||
<p xml:lang="ca">El NeoChat ara permet filtrar i entrar a una sala directament des del KRunner (Cerca del Plasma). A part d'això també hi ha diverses correccions d'errors pel que fa a les notificacions d'escriptura.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat ara permet filtrar i entrar a una sala directament des de KRunner (Busca de Plasma). A part d'açò també hi ha diverses esmenes d'errors pel que fa a les notificacions d'escriptura.</p>
|
||||
<p xml:lang="ka">NeoChat ახლა საშუალებას გაძლევთ, გაფილტროთ და შეხვიდეთ ოთახში პირდაპი KRunner-დან (Plasma-ის ძებნა). ამის გარდა ასევე გასწორდა სხვადასხვა შეცდომა კრეფის გაფრთხილების შესახებ.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat agora permite filtrar e entrar em uma sala diretamente do KRunner (Busca do Plasma). Além disso, também foram feitas diversas correções de bugs relacionados às notificações de digitação.</p>
|
||||
<p xml:lang="ru">Добавлена возможность фильтрации и входа в комнату прямо из KRunner (поиск Plasma). Кроме того, исправлены различные ошибки, связанные с уведомлениями о наборе текста.</p>
|
||||
<p xml:lang="sl">NeoChat zdaj omogoča filtriranje in vstop v sobo neposredno iz KRunnerja (iskanje v Plasmi). Poleg tega so bile odpravljene tudi različne napake v zvezi z obvestili o tipkanju.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="22.02" date="2022-02-09">
|
||||
<description>
|
||||
<p>NeoChat 22.02 focus on stability and adds a few quality of life improvements</p>
|
||||
<p xml:lang="ar">يركز نيوتشات 22.02 على الاستقرار ويضيف بعض تحسينات جودة الاستخدام</p>
|
||||
<p xml:lang="ca">El NeoChat 22.02 se centra en l'estabilitat i afegeix algunes millores de qualitat de vida</p>
|
||||
<p xml:lang="ca-valencia">NeoChat 22.02 se centra en l'estabilitat i afig algunes millores de qualitat de vida</p>
|
||||
<p xml:lang="ka">NeoChat 22.02-ის ფოკუსია სტაბილურობა და ამატებს რამდენიმე ცხოვრების დონის გაუმჯობესებას</p>
|
||||
<p xml:lang="pt-BR">O NeoChat 22.02 foca na estabilidade e adiciona algumas melhorias de usabilidade</p>
|
||||
<p xml:lang="ru">В NeoChat 22.02 основное внимание уделено стабильности и добавлено несколько улучшений для удобства использования.</p>
|
||||
<p xml:lang="sl">NeoChat 22.02 se osredotoča na stabilnost in dodaja nekaj izboljšav kakovosti življenja</p>
|
||||
<ul>
|
||||
<li>Add support for minimizing to system tray on startup</li>
|
||||
<li xml:lang="ar">إضافة دعم التصغير إلى صينية النظام عند بدء التشغيل</li>
|
||||
<li xml:lang="ca">Afegeix la implementació per a minimitzar a la safata del sistema en iniciar</li>
|
||||
<li xml:lang="ca-valencia">Afig la implementació per a minimitzar a la safata del sistema en iniciar</li>
|
||||
<li xml:lang="ka">დაემატა გაშვებისას საათთან ჩაკეცვის მხარდაჭერა</li>
|
||||
<li xml:lang="pt-BR">Adicionado suporte para minimizar para a bandeja do sistema na inicialização</li>
|
||||
<li xml:lang="ru">Добавлена поддержка сворачивания в системный лоток при запуске;</li>
|
||||
<li xml:lang="sl">Dodajte podporo za minimitziranje v sistemsko vrstico ob zagonu</li>
|
||||
<li>Improved internet connectivity check</li>
|
||||
<li xml:lang="ar">تحسين فحص الاتصال بالإنترنت</li>
|
||||
<li xml:lang="ca">Millora de la verificació de la connectivitat a Internet</li>
|
||||
<li xml:lang="ca-valencia">Millora de la verificació de la connectivitat a Internet</li>
|
||||
<li xml:lang="es">Se ha mejorado la comprobación de la conectividad con internet.</li>
|
||||
<li xml:lang="ka">გაუმჯობესდა ინტერნეტკავშირის შემოწმება</li>
|
||||
<li xml:lang="pt-BR">Verificação de conectividade de internet aprimorada</li>
|
||||
<li xml:lang="ru">Улучшена проверка подключения к Интернету;</li>
|
||||
<li xml:lang="sl">Izboljšano preverjanje internetne povezave</li>
|
||||
<li>Add support for sharing images and files with other apps (Nextcloud, Imgur, ...)</li>
|
||||
<li xml:lang="ar">إضافة دعم مشاركة الصور والملفات مع تطبيقات أخرى (نكست كلاود، إمجور، ...)</li>
|
||||
<li xml:lang="ca">Afegeix la implementació per a compartir imatges i fitxers amb altres aplicacions (Nextcloud, Imgur...)</li>
|
||||
<li xml:lang="ca-valencia">Afig la implementació per a compartir imatges i fitxers amb altres aplicacions (Nextcloud, Imgur…)</li>
|
||||
<li xml:lang="es">Se ha añadido compatibilidad para compartir imágenes y archivos con otras aplicaciones (Nextcloud, Imgur, etc.).</li>
|
||||
<li xml:lang="ka">დაემატა გამოსახულებებისა და ფაილების სხვა აპებთან (Nextcloud, Imgur,...) გაზიარების მხარდაჭერა</li>
|
||||
<li xml:lang="pt-BR">Adicionado suporte para compartilhamento de imagens e arquivos com outros aplicativos (Nextcloud, Imgur, ...)</li>
|
||||
<li xml:lang="ru">Добавлена возможность обмена изображениями и файлами с другими приложениями (Nextcloud, Imgur, и прочими службами);</li>
|
||||
<li xml:lang="sl">Doda podporo za deljenje slik in datotek z drugimi aplikacijami (Nextcloud, Imgur, ...)</li>
|
||||
<li>Implement adding labels for account. This allow for an easier organization when using multiple accounts.</li>
|
||||
<li xml:lang="ar">تطبيق إضافة لصائق للحساب. يسمح هذا بتنظيم أسهل عند استخدام حسابات متعددة.</li>
|
||||
<li xml:lang="ca">Implementa l'addició d'etiquetes al compte. Això permet una organització més fàcil quan s'utilitzen diversos comptes.</li>
|
||||
<li xml:lang="ca-valencia">Implementa l'addició d'etiquetes al compte. Açò permet una organització més fàcil quan s'utilitzen diversos comptes.</li>
|
||||
<li xml:lang="ka">ახლა ანგარიშებს შეიძლიათ, ჭდეები დაამატოთ. ეს აადვილებს ორგანიზებას, როცა ერთზე მეტ ანგარიშს იყენებთ.</li>
|
||||
<li xml:lang="pt-BR">Implementada a adição de etiquetas para contas. Isso facilita a organização ao usar várias contas.</li>
|
||||
<li xml:lang="ru">Реализовано добавление меток для учётных записей; это упрощает организацию при использовании нескольких учётных записей.</li>
|
||||
<li xml:lang="sl">Implementira dodajanje oznak za račun. To omogoča lažjo organizacijo pri uporabi več računov.</li>
|
||||
<li>Redesign of our config dialogs to follow the new Plasma System Settings style</li>
|
||||
<li xml:lang="ar">إعادة تصميم حوارات الضبط لاتباع نمط إعدادات نظام بلازما الجديد</li>
|
||||
<li xml:lang="ca">Redisseny dels diàlegs de configuració per a seguir l'estil nou de l'arranjament del sistema del Plasma</li>
|
||||
<li xml:lang="ca-valencia">Redisseny dels diàlegs de configuració per a seguir l'estil nou de Configuració del sistema de Plasma</li>
|
||||
<li xml:lang="ka">შეიცვალა დიზაინი კონფიგურაციის დიალოგებისთვის, რომ ისინი Plasma-ის სისტემური პარამეტრების სტილს მიჰყვებოდნენ</li>
|
||||
<li xml:lang="pt-BR">Redesenho das caixas de diálogo de configuração para seguir o novo estilo das Configurações do Sistema do Plasma.</li>
|
||||
<li xml:lang="ru">Реализован редизайн диалогов настройки в соответствии со стилем нового приложения «Параметры системы» Plasma;</li>
|
||||
<li xml:lang="sl">Preoblikovanje naših konfiguracijskih pogovornih oken, da sledijo novemu slogu nastavitev sistema Plasma</li>
|
||||
<li>Fix various others issues and small feature requests. Decreasing the total amount of open issues by 20%.</li>
|
||||
<li xml:lang="ar">إصلاح قضايا متنوعة أخرى وطلبات ميزات صغيرة، مما يقلل إجمالي القضايا المفتوحة بنسبة 20%.</li>
|
||||
<li xml:lang="ca">Corregeix diversos problemes i peticions de funcionalitats petites. Disminució de la quantitat total de problemes oberts en un 20%.</li>
|
||||
<li xml:lang="ca-valencia">Corregix diversos problemes i peticions de característiques xicotetes. Disminució de la quantitat total de problemes oberts en un 20%.</li>
|
||||
<li xml:lang="ka">გასწორდა სხვადასხვა შეცდომები და პატარა ფუნქციის მოთხოვნები. ღია პრობლემების ჯამური რაოდენობა შემცირდა 20%-ით.</li>
|
||||
<li xml:lang="pt-BR">Corrigido diversos outros problemas e implementação de pequenas solicitações de melhorias. Reduzido o número total de bugs em aberto em 20%.</li>
|
||||
<li xml:lang="ru">Исправлены прочие ошибки и реализованы небольшие запросы на новые возможности; общее количество открытых проблем сокращено на 20%.</li>
|
||||
<li xml:lang="sl">Odpravi različne druge težave in manjše zahteve za zmožnosti. Zmanjšajte skupno število odprtih težav za 20%.</li>
|
||||
</ul>
|
||||
</description>
|
||||
<url>https://www.plasma-mobile.org/2022/02/09/plasma-mobile-gear-22-02/#neochat</url>
|
||||
@@ -905,144 +614,22 @@
|
||||
<release version="21.12" date="2021-12-07">
|
||||
<description>
|
||||
<p>NeoChat 21.12 brings lots of new features and fixes</p>
|
||||
<p xml:lang="ar">يوفر نيوتشات 21.12 الكثير من الميزات والإصلاحات الجديدة</p>
|
||||
<p xml:lang="ca">El NeoChat 21.12 aporta moltes funcionalitats noves i correccions</p>
|
||||
<p xml:lang="ca-valencia">NeoChat 21.12 aporta moltes característiques noves i correccions</p>
|
||||
<p xml:lang="es">NeoChat 21.12 proporciona muchas funciones nuevas y correcciones.</p>
|
||||
<p xml:lang="ka">NeoChat 21.12 ბევრ ახალი ფუნქციას და შეცდომების გასწორებას შეიცავს</p>
|
||||
<p xml:lang="pt-BR">O NeoChat 21.12 traz muitas novidades e correções</p>
|
||||
<p xml:lang="ru">В NeoChat 21.12 добавлено множество новых возможностей и исправлений</p>
|
||||
<p xml:lang="sl">NeoChat 21.12 prinaša veliko novih zmožnosti in popravkov</p>
|
||||
<ul>
|
||||
<li>Solved various problems related to login, logout and account switching</li>
|
||||
<li xml:lang="ar">حُلّت مشكلات متنوعة متعلقة بالولوج والخروج وتبديل الحسابات</li>
|
||||
<li xml:lang="ca">S'han resolt diversos problemes relacionats amb l'inici de sessió, la sortida i el canvi de compte</li>
|
||||
<li xml:lang="ca-valencia">S'han resolt diversos problemes relacionats amb l'inici de sessió, l'eixida i el canvi de compte</li>
|
||||
<li xml:lang="es">Se han solucionado diversos problemas relacionados con el inicio y el cierre de sesión y con el cambio de cuenta.</li>
|
||||
<li xml:lang="ka">გადაიჭრა შესვლასთან, გასვლასთან და ანგარიშის გადართვასთან დაკავშირებული სხვადასხვა პრობლემა</li>
|
||||
<li xml:lang="pt-BR">Resolvidos diversos problemas relacionados a login, logout e troca de contas</li>
|
||||
<li xml:lang="ru">Устранены проблемы, связанные с входом, выходом и переключением учётных записей;</li>
|
||||
<li xml:lang="sl">Rešene različne težave, povezane s prijavo, odjavo in preklapljanjem računov</li>
|
||||
<li>Fixed a few problems in the timeline layout</li>
|
||||
<li xml:lang="ar">أُصلحت بعض المشكلات في مخطط الخط الزمني</li>
|
||||
<li xml:lang="ca">S'han corregit alguns problemes en la disposició de la línia de temps</li>
|
||||
<li xml:lang="ca-valencia">S'han corregit alguns problemes en la disposició de la línia de temps</li>
|
||||
<li xml:lang="es">Se han corregido varios problemas en la disposición de la línea de tiempo</li>
|
||||
<li xml:lang="ka">გასწორდა დროის ხაზის განლაგების რამდენიმე პრობლემა</li>
|
||||
<li xml:lang="pt-BR">Corrigidos alguns problemas no layout da linha do tempo</li>
|
||||
<li xml:lang="ru">Исправлены некоторые проблемы в расположении ленты событий;</li>
|
||||
<li xml:lang="sl">Odpravljenih je bilo nekaj težav v postavitvi časovnice</li>
|
||||
<li>Added Spell checking while writing a message</li>
|
||||
<li xml:lang="ar">أُضيف التدقيق الإملائي أثناء كتابة الرسالة</li>
|
||||
<li xml:lang="ca">S'ha afegit la verificació ortogràfica mentre s'escriu un missatge</li>
|
||||
<li xml:lang="ca-valencia">S'ha afegit la verificació ortogràfica mentre s'escriu un missatge</li>
|
||||
<li xml:lang="es">Se ha añadido comprobación ortográfica durante la escritura de mensajes.</li>
|
||||
<li xml:lang="ka">დაემატა მართლწერის შემოწმება შეტყობინების წერისას</li>
|
||||
<li xml:lang="pt-BR">Adicionada verificação ortográfica durante a escrita de mensagens</li>
|
||||
<li xml:lang="ru">Добавлена проверка орфографии при написании сообщения;</li>
|
||||
<li xml:lang="sl">Dodano preverjanje črkovanja med pisanjem sporočila</li>
|
||||
<li>Improved Settings pages</li>
|
||||
<li xml:lang="ar">تحسين صفحات الإعدادات</li>
|
||||
<li xml:lang="ca">Pàgines de configuració millorades</li>
|
||||
<li xml:lang="ca-valencia">Pàgines de configuració millorades</li>
|
||||
<li xml:lang="es">Se han mejorado las páginas de preferencias.</li>
|
||||
<li xml:lang="ka">გაუმჯობესდა მორგების გვერდები</li>
|
||||
<li xml:lang="pt-BR">Páginas de configurações aprimoradas</li>
|
||||
<li xml:lang="ru">Улучшены страницы настроек;</li>
|
||||
<li xml:lang="sl">Izboljšane strani z nastavitvami</li>
|
||||
<li>Many improvements to the android and general mobile support</li>
|
||||
<li xml:lang="ar">تحسينات كثيرة لدعم أندرويد والأجهزة المحمولة بشكل عام</li>
|
||||
<li xml:lang="ca">Moltes millores a l'Android i suport general de mòbils</li>
|
||||
<li xml:lang="ca-valencia">Moltes millores a Android i suport general de mòbils</li>
|
||||
<li xml:lang="ka">ბევრი გაუმჯობესება Android-ის და ზოგადი მობილური მხარდაჭერაში</li>
|
||||
<li xml:lang="pt-BR">Muitas melhorias no Android e no suporte geral para dispositivos móveis</li>
|
||||
<li xml:lang="ru">Многочисленные улучшения для Android и мобильных устройств в целом;</li>
|
||||
<li xml:lang="sl">Številne izboljšave podpore za Android in splošno mobilno tehnologijo</li>
|
||||
<li>Show blurhashes while images load</li>
|
||||
<li xml:lang="ar">عرض blurhashes أثناء تحميل الصور</li>
|
||||
<li xml:lang="ca">Mostra «blurhashes» mentre es carreguen les imatges</li>
|
||||
<li xml:lang="ca-valencia">Mostra «blurhashes» mentre es carreguen les imatges</li>
|
||||
<li xml:lang="ka">დაბინდული ადგილების ჩვენება, სანამ გამოსახულებები ჩაიტვირთება</li>
|
||||
<li xml:lang="pt-BR">Exibir os ícones de desfoque enquanto as imagens carregam</li>
|
||||
<li xml:lang="ru">Отображение размытых хешей во время загрузки изображений;</li>
|
||||
<li xml:lang="sl">Prikaži zamegljene črtice med nalaganjem slik</li>
|
||||
<li>Support showing custom emojis</li>
|
||||
<li xml:lang="ar">دعم عرض الرموز التعبيرية المخصصة</li>
|
||||
<li xml:lang="ca">Permet mostrar emojis personalitzats</li>
|
||||
<li xml:lang="ca-valencia">Permet mostrar emoji personalitzats</li>
|
||||
<li xml:lang="es">Compatibilidad con emojis personalizados.</li>
|
||||
<li xml:lang="ka">მორგებული ემოჯიების ჩვენების მხარდაჭერა</li>
|
||||
<li xml:lang="pt-BR">Suporte para exibição de emojis personalizados</li>
|
||||
<li xml:lang="ru">Отображение пользовательских эмодзи;</li>
|
||||
<li xml:lang="sl">Podpora za prikazovanje čustvenčkov po meri</li>
|
||||
<li>Added a global menu</li>
|
||||
<li xml:lang="ar">أُضيفت قائمة عامة</li>
|
||||
<li xml:lang="ca">S'ha afegit un menú global</li>
|
||||
<li xml:lang="ca-valencia">S'ha afegit un menú global</li>
|
||||
<li xml:lang="es">Se ha añadido un menú global.</li>
|
||||
<li xml:lang="ka">დაემატა გლობალური მენიუ</li>
|
||||
<li xml:lang="pt-BR">Adicionado um menu global</li>
|
||||
<li xml:lang="ru">Добавлено глобальное меню;</li>
|
||||
<li xml:lang="sl">Dodan globalni meni</li>
|
||||
<li>Added support for spoilers</li>
|
||||
<li xml:lang="ar">أُضيف دعم المحتوى المحروق</li>
|
||||
<li xml:lang="ca">S'ha afegit la implementació per als espòilers</li>
|
||||
<li xml:lang="ca-valencia">S'ha afegit la implementació per als espòilers</li>
|
||||
<li xml:lang="ka">დაემატა სპოილერების მხარდაჭერა</li>
|
||||
<li xml:lang="pt-BR">Adicionado suporte para spoilers</li>
|
||||
<li xml:lang="ru">Добавлена поддержка скрытого текста;</li>
|
||||
<li xml:lang="sl">Dodana podpora za spojlerje</li>
|
||||
<li>Added a quick switcher to switch between rooms</li>
|
||||
<li xml:lang="ar">أُضيف مبدل سريع للتبديل بين الغرف</li>
|
||||
<li xml:lang="ca">S'ha afegit un commutador ràpid per a canviar entre sales</li>
|
||||
<li xml:lang="ca-valencia">S'ha afegit un commutador ràpid per a canviar entre sales</li>
|
||||
<li xml:lang="es">Se ha añadido un selector rápido para cambiar de sala.</li>
|
||||
<li xml:lang="ka">დაემატა სწრაფი გადამრთველი ოთახებს შორის</li>
|
||||
<li xml:lang="pt-BR">Adicionado um botão de troca rápida para alternar entre salas</li>
|
||||
<li xml:lang="ru">Добавлен быстрый переключатель для перехода между комнатами;</li>
|
||||
<li xml:lang="sl">Dodan hitri preklopnik za preklapljanje med sobami</li>
|
||||
<li>Added support for an optional fancy blur background effect</li>
|
||||
<li xml:lang="ar">أُضيف دعم لتأثير خلفية ضبابية أنيقة اختياري</li>
|
||||
<li xml:lang="ca">S'ha afegit la implementació per a un efecte de difuminat de fons opcional</li>
|
||||
<li xml:lang="ca-valencia">S'ha afegit la implementació per a un efecte de difuminat de fons opcional</li>
|
||||
<li xml:lang="ka">დაემატა არასავალდებულო მდიდრული ბუნდოვანი ფონის ეფექტის მხარდაჭერა</li>
|
||||
<li xml:lang="pt-BR">Adicionada a opção de um efeito de desfoque de fundo sofisticado</li>
|
||||
<li xml:lang="ru">Добавлена поддержка необязательного эффекта размытого фона;</li>
|
||||
<li xml:lang="sl">Dodana podpora za izbirni učinek zameglitve ozadja</li>
|
||||
<li>Resizable left and right drawers</li>
|
||||
<li xml:lang="ar">أدراج يمنى ويسرى قابلة لتغيير الحجم</li>
|
||||
<li xml:lang="ca">Calaixos esquerra i dreta redimensionables</li>
|
||||
<li xml:lang="ca-valencia">Calaixos esquerra i dreta redimensionables</li>
|
||||
<li xml:lang="ka">ზომაცვლადი მარცხენა და მარჯვენა უჯრები</li>
|
||||
<li xml:lang="pt-BR">Gavetas redimensionáveis à esquerda e à direita</li>
|
||||
<li xml:lang="ru">Изменяемые размеры левой и правой панелей;</li>
|
||||
<li xml:lang="sl">Spremenljiva velikost levega in desnega predala</li>
|
||||
<li>Added Syntax highlighting in raw json messages</li>
|
||||
<li xml:lang="ar">أُضيف تمييز الصيغة في رسائل json الخام</li>
|
||||
<li xml:lang="ca">S'ha afegit el ressaltat de sintaxi en els missatges JSON en brut</li>
|
||||
<li xml:lang="ca-valencia">S'ha afegit el ressaltat de sintaxi en els missatges JSON en brut</li>
|
||||
<li xml:lang="ka">დაემატა სინტაქსის გამოკვეთა დაუმუშავებელ JSON შეტყობინებებში</li>
|
||||
<li xml:lang="pt-BR">Adicionada a coloração de sintaxe em mensagens JSON brutas</li>
|
||||
<li xml:lang="ru">Добавлена подсветка синтаксиса в необработанных JSON-сообщениях;</li>
|
||||
<li xml:lang="sl">Dodano označevanje skladnje v sporočilih raw json</li>
|
||||
<li>Better wayland support</li>
|
||||
<li xml:lang="ar">دعم أفضل لويلاند</li>
|
||||
<li xml:lang="ca">Millor suport del Wayland</li>
|
||||
<li xml:lang="ca-valencia">Millor suport de Wayland</li>
|
||||
<li xml:lang="es">Mejor compatibilidad con Wayland.</li>
|
||||
<li xml:lang="ka">Wayland-ის უკეთესი მხარდაჭერა</li>
|
||||
<li xml:lang="pt-BR">Melhor suporte ao Wayland</li>
|
||||
<li xml:lang="ru">Улучшена поддержка Wayland;</li>
|
||||
<li xml:lang="sl">Boljša podpora za Wayland</li>
|
||||
<li>Improved file reception and download</li>
|
||||
<li xml:lang="ar">تحسين استقبال الملفات وتنزيلها</li>
|
||||
<li xml:lang="ca">Recepció i baixada de fitxers millorades</li>
|
||||
<li xml:lang="ca-valencia">Recepció i baixada de fitxers millorades</li>
|
||||
<li xml:lang="es">Se ha mejorado la recepción y la descarga de archivos.</li>
|
||||
<li xml:lang="ka">გაუმჯობესდა ფაილების მიღება და გადმოწერა</li>
|
||||
<li xml:lang="pt-BR">Recepção e download de arquivos aprimorados</li>
|
||||
<li xml:lang="ru">Улучшены приём и загрузка файлов;</li>
|
||||
<li xml:lang="sl">Izboljšan sprejem in prenos datotek</li>
|
||||
</ul>
|
||||
</description>
|
||||
<url>https://www.plasma-mobile.org/2021/12/07/plasma-mobile-gear-21-12/</url>
|
||||
@@ -1050,29 +637,8 @@
|
||||
<release version="1.2.0" date="2021-06-01">
|
||||
<description>
|
||||
<p>NeoChat 1.2 brings a major redesign of the user interface. The chat page is now using bubbles for the messages and the input component was completely rewritten with a nicer look as well.</p>
|
||||
<p xml:lang="ar">يوفر نيوتشات 1.2 إعادة تصميم كبرى لواجهة المستخدم. تستخدم صفحة الدردشة الآن فقاعات للرسائل، كما أُعيدت كتابة مكون الإدخال بالكامل بمظهر أجمل.</p>
|
||||
<p xml:lang="ca">El NeoChat 1.2 aporta un redisseny important de la interfície d'usuari. La pàgina de xat ara utilitza bombolles per als missatges i el component d'entrada s'ha reescrit completament amb un aspecte més agradable també.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat 1.2 aporta un redisseny important de la interfície d'usuari. La pàgina de xat ara utilitza bambolles per als missatges i el component d'entrada s'ha reescrit completament amb un aspecte més agradable també.</p>
|
||||
<p xml:lang="ka">NeoChat 1.2 მომხმარებლის ინტერფეისის დიზაინის თითქმის სრულ ცვლილებას შეიცავს. ჩატის გვერდი ახლა შეტყობინებებისთვის ბუშტებს იყენებს და შეყვანის კომპონენტი მთლიანად თავიდანაა დაწერილი, რომ უკეთ გამოიყურებოდეს.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat 1.2 traz uma grande reformulação da interface do usuário. A página de bate-papo agora utiliza balões para as mensagens e o componente de entrada foi completamente reescrito com um visual mais agradável.</p>
|
||||
<p xml:lang="ru">В версии NeoChat 1.2 полностью изменён пользовательский интерфейс. На странице чата сообщения отображаются в виде пузырей, а компонент ввода был полностью переписан и получил более приятный внешний вид.</p>
|
||||
<p xml:lang="sl">NeoChat 1.2 prinaša veliko prenovo uporabniškega vmesnika. Stran za klepet zdaj uporablja mehurčke za sporočila, vhodna komponenta pa je bila popolnoma prepisana in ima lepši videz.</p>
|
||||
<p>It's now possible to send custom reactions by replying to a comment with /react <message>.</p>
|
||||
<p xml:lang="ar">أصبح من الممكن الآن إرسال تفاعلات مخصصة عبر الرد على تعليق بالأمر /react <message>.</p>
|
||||
<p xml:lang="ca">Ara és possible enviar reaccions personalitzades responent a un comentari amb /react <missatge>.</p>
|
||||
<p xml:lang="ca-valencia">Ara és possible enviar reaccions personalitzades responent a un comentari amb /react <missatge>.</p>
|
||||
<p xml:lang="ka">ახლა შესაძლებელია მორგებული რეაქციების გაგზავნა კომენტარზე ბრძანებით /react <message> პასუხის საშუალებით.</p>
|
||||
<p xml:lang="pt-BR">Agora é possível enviar reações personalizadas respondendo a um comentário com /react <mensagem>.</p>
|
||||
<p xml:lang="ru">Теперь можно отправлять собственные реакции, ответив на сообщение командой /react <сообщение>.</p>
|
||||
<p xml:lang="sl">Zdaj je mogoče poslati odzive po meri tako, da na komentar odgovorite z /react <message>.</p>
|
||||
<p>NeoChat now supports opening Matrix URIs from your browser.</p>
|
||||
<p xml:lang="ar">يدعم نيوتشات الآن فتح معرفات Matrix URI من المتصفح.</p>
|
||||
<p xml:lang="ca">El NeoChat ara permet obrir els URI de Matrix des del navegador.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat ara permet obrir els URI de Matrix des del navegador.</p>
|
||||
<p xml:lang="ka">NeoChat-ს ახლა მატრიცის URI-ების გახსნა შეუძლია თქვენი ბრაუზერიდან.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat agora suporta a abertura de URIs do Matrix a partir do seu navegador.</p>
|
||||
<p xml:lang="ru">В NeoChat добавлена поддержка открытия URI Matrix из браузера.</p>
|
||||
<p xml:lang="sl">NeoChat zdaj podpira odpiranje URI-jev Matrix iz vašega brskalnika.</p>
|
||||
</description>
|
||||
<url>https://carlschwan.eu/2021/06/01/neochat-1.2/</url>
|
||||
</release>
|
||||
@@ -1080,108 +646,23 @@
|
||||
<release version="1.1.0" date="2021-02-22">
|
||||
<description>
|
||||
<p>Probably the highlight of this release is the completely new login page. It detects the server configuration based on your Matrix Id. This allows you to login to servers requiring Single Sign On (SSO) (like the Mozilla or the incoming Fedora Matrix instance).</p>
|
||||
<p xml:lang="ar">لعل أبرز ما في هذا الإصدار هو صفحة الولوج الجديدة كلياً. فهي تكتشف ضبط الخادم بناءً على معرف ماتركس الخاص بك. يتيح هذا الولوج إلى الخوادم التي تتطلب الولوج الموحد (SSO) (مثل خوادم موزيلا أو خادم ماتركس القادم لفيدورا).</p>
|
||||
<p xml:lang="ca">Probablement el més destacat d'aquest llançament és la pàgina d'inici de sessió completament nova. Detecta la configuració del servidor basant-se en l'identificador de Matrix. Això permet iniciar sessió en servidors que requereixen inici de sessió únic (SSO) (com el Mozilla o la instància d'entrada de Matrix de Fedora).</p>
|
||||
<p xml:lang="ca-valencia">Probablement el més destacat d'este llançament és la pàgina d'inici de sessió completament nova. Detecta la configuració del servidor basant-se en l'identificador de Matrix. Açò permet iniciar sessió en servidors que requerixen inici de sessió únic (SSO) (com Mozilla o la instància d'entrada de Matrix de Fedora).</p>
|
||||
<p xml:lang="ka">ალბათ ამ ვერსიის გამოკვეთილი ცვლილება სრულიად ახალი შესვლის გვერდია. ის სერვერის კონფიგურაციას თქვენი მატრიცის ID-ის მიხედვით ადგენს. ეს საშუალებას გაძლევთ, შეხვიდეთ სერვერებზე, რომლებიც SSO-ით (მაგ Mozilla, ან შემომავალი Fedora-ის მატრიცის გაშვებული ასლი) შესვლას ითხოვს.</p>
|
||||
<p xml:lang="pt-BR">Provavelmente, o grande destaque desta versão é a página de login completamente nova. Ela detecta a configuração do servidor com base no seu ID do Matrix. Isso permite que você faça login em servidores que exigem Single Sign-On (SSO) (como a instância do Mozilla Matrix ou a futura instância do Fedora Matrix).</p>
|
||||
<p xml:lang="ru">Главным нововведением этой версии стала полностью переработанная страница входа. Она определяет конфигурацию сервера по вашему идентификатору Matrix, что позволяет входить на серверы с единой системой аутентификации (например, Mozilla или готовящийся к запуску экземпляр Fedora Matrix).</p>
|
||||
<p xml:lang="sl">Verjetno vrhunec te izdaje je popolnoma nova prijavna stran. Zazna konfiguracijo strežnika na podlagi vašega Matrix ID-ja. To vam omogoča prijavo na strežnike, ki zahtevajo enotno prijavo (SSO) (kot sta Mozilla ali prihajajoči pojavek Fedora Matrix).</p>
|
||||
<p>Servers that require agreeing to the TOS before usage are correctly detected now and redirect to their TOS webpage, allowing the user to agree to them instead of silently failing to load the account.</p>
|
||||
<p xml:lang="ar">تُكتشف الخوادم التي تتطلب الموافقة على شروط الخدمة قبل الاستخدام بشكل صحيح الآن وتُحوّل إلى صفحة شروط الخدمة الخاصة بها، مما يسمح للمستخدم بالموافقة عليها بدلاً من فشل تحميل الحساب بصمت.</p>
|
||||
<p xml:lang="ca">Els servidors que requereixen acceptar el TOS abans de l'ús ara es detecten correctament i redirigeixen a la seva pàgina web del TOS, que permet a l'usuari acceptar-lo en lloc de no carregar el compte en silenci.</p>
|
||||
<p xml:lang="ca-valencia">Els servidors que requerixen acceptar TOS abans de l'ús ara es detecten correctament i redirigixen a la seua pàgina web de TOS, que permet a l'usuari acceptar-lo en lloc de no carregar el compte en silenci.</p>
|
||||
<p xml:lang="ka">სერვერები, რომლებიც გამოყენების პირობებზე დათანხმებას ითხოვენ, ახლა სწორად არიან აღმოჩენილები და გადაგამისამართებთ მათი გამოყენების პირობების ვებგვერდზე, სადაც მომხმარებელს საშუალება აქვს, დაეთანხმოს მას იმის მაგიერ, ანგარიშის ჩატვირთვა შეცდომის გარეშე, ჩუმად ჩავარდეს.</p>
|
||||
<p xml:lang="pt-BR">Os servidores que exigem a aceitação dos Termos de Serviço antes do uso agora são detectados corretamente e redirecionam para a página dos Termos de Serviço, permitindo que o usuário os aceite em vez de simplesmente não conseguir carregar a conta.</p>
|
||||
<p xml:lang="ru">Теперь серверы, требующие согласия с условиями использования перед началом работы, определяются корректно и перенаправляют пользователя на соответствующую веб-страницу, позволяя принять условия вместо аварийного завершения загрузки учётной записи.</p>
|
||||
<p xml:lang="sl">Strežniki, ki pred uporabo zahtevajo strinjanje s pogoji uporabe, so zdaj pravilno zaznani in preusmerjajo na njihovo spletno stran s pogoji uporabe, kar uporabniku omogoča, da se z njimi strinja, namesto da se račun tiho ne naloži.</p>
|
||||
<p>It is now possible to open a room into a new window. This allows you to view and interact with multiple rooms at the same time.</p>
|
||||
<p xml:lang="ar">أصبح من الممكن الآن فتح غرفة في نافذة جديدة. يتيح هذا عرض غرف متعددة والتفاعل معها في الوقت ذاته.</p>
|
||||
<p xml:lang="ca">Ara és possible obrir una sala en una finestra nova. Això permet veure i interactuar amb diverses sales alhora.</p>
|
||||
<p xml:lang="ca-valencia">Ara és possible obrir una sala en una finestra nova. Açò permet veure i interactuar amb diverses sales alhora.</p>
|
||||
<p xml:lang="ka">ახლა შესაძლებელია, ოთახი ცალკე ფანჯარაში გახსნათ. ეს საშუალებას გაძლევთ, ერთდროულად მრავალ ოთახში ისაუბროთ.</p>
|
||||
<p xml:lang="pt-BR">Agora é possível abrir uma sala em uma nova janela. Isso permite visualizar e interagir com várias salas ao mesmo tempo.</p>
|
||||
<p xml:lang="ru">Добавлена возможность открытия комнаты в отдельном окне для одновременного просмотра и взаимодействия с несколькими комнатами.</p>
|
||||
<p xml:lang="sl">Zdaj je mogoče odpreti sobo v novem oknu. To vam omogoča ogled in interakcijo z več sobami hkrati.</p>
|
||||
<p>We added a few commands to NeoChat (/shrug, /lenny, /join, /ignore, ...).</p>
|
||||
<p xml:lang="ar">أُضيفت بضعة أوامر لنيوتشات (/shrug, /lenny, /join, /ignore, ...).</p>
|
||||
<p xml:lang="ca">Hem afegit algunes ordres al NeoChat (/shrug, /lenny, /join, /ignore...).</p>
|
||||
<p xml:lang="ca-valencia">Hem afegit algunes ordres a NeoChat (/shrug, /lenny, /join, /ignore…).</p>
|
||||
<p xml:lang="ka">NeoChat-ს რამდენიმე ბრძანება (/shrug, /lenny, /join, /ignore, ...) დავამატეთ.</p>
|
||||
<p xml:lang="pt-BR">Adicionamos alguns comandos ao NeoChat (/shrug, /lenny, /join, /ignore, ...).</p>
|
||||
<p xml:lang="ru">Добавлены команды для NeoChat (/shrug, /lenny, /join, /ignore, …).</p>
|
||||
<p xml:lang="sl">V NeoChat smo dodali nekaj ukazov (/shrug, /lenny, /join, /ignore, ...).</p>
|
||||
<p>We improved the Plasma integration a bit. Now the number of unread messages is displayed in the Plasma Taskbar.</p>
|
||||
<p xml:lang="ar">حُسّن تكامل بلازما قليلاً. يُعرض الآن عدد الرسائل غير المقروءة في شريط مهام بلازما.</p>
|
||||
<p xml:lang="ca">Hem millorat una mica la integració amb el Plasma. Ara es mostra el nombre de missatges sense llegir a la barra de tasques del Plasma.</p>
|
||||
<p xml:lang="ca-valencia">Hem millorat una mica la integració amb Plasma. Ara es mostra el nombre de missatges sense llegir a la barra de tasques de Plasma.</p>
|
||||
<p xml:lang="ka">ოდნავ გავაუმჯობესეთ Plasma-ის ინტეგრაცია. ახლა წაუკითხავი შეტყობინებების რაოდენობა Plasma-ის ამოცანათა პანელზე გამოჩნდება.</p>
|
||||
<p xml:lang="pt-BR">Aprimoramos um pouco a integração com o Plasma. Agora, o número de mensagens não lidas é exibido na barra de tarefas do Plasma.</p>
|
||||
<p xml:lang="ru">Улучшена интеграция с Plasma. Теперь число непрочитанных сообщений отображается в панели задач Plasma.</p>
|
||||
<p xml:lang="sl">Nekoliko smo izboljšali integracijo s Plasmo. Zdaj je število neprebranih sporočil prikazano v opravilni vrstici Plasme.</p>
|
||||
</description>
|
||||
<url>https://carlschwan.eu/2021/02/22/neochat-1.1/</url>
|
||||
</release>
|
||||
<release version="1.0.1" date="2021-01-13">
|
||||
<description>
|
||||
<p>This version fixes several bugs.</p>
|
||||
<p xml:lang="ar">تُصلح هذه النسخة عدة علل.</p>
|
||||
<p xml:lang="ca">Aquesta versió corregeix diversos errors.</p>
|
||||
<p xml:lang="ca-valencia">Esta versió corregix diversos errors.</p>
|
||||
<p xml:lang="es">Esta versión corrige algunos fallos.</p>
|
||||
<p xml:lang="ka">ეს ვერსია რამდენიმე შეცდომას ასწორებს.</p>
|
||||
<p xml:lang="pt-BR">Esta versão corrige vários bugs.</p>
|
||||
<p xml:lang="ru">В этой версии исправлены несколько ошибок.</p>
|
||||
<p xml:lang="sl">Ta različica odpravlja več napak.</p>
|
||||
<ul>
|
||||
<li>NeoChat doesn't require a .well-know configuration in the server to work.</li>
|
||||
<li xml:lang="ar">لا يتطلب نيوتشات ضبط .well-know في الخادم ليعمل.</li>
|
||||
<li xml:lang="ca">El NeoChat no requereix una configuració «.well-know» al servidor per a funcionar.</li>
|
||||
<li xml:lang="ca-valencia">NeoChat no requerix una configuració «.well-know» al servidor per a funcionar.</li>
|
||||
<li xml:lang="ka">NeoChat-ს სამუშაოდ სერვერზე .well-know კონფიგურაცია არ სჭირდება.</li>
|
||||
<li xml:lang="pt-BR">O NeoChat não requer uma configuração .well-know no servidor para funcionar.</li>
|
||||
<li xml:lang="ru">Для работы NeoChat не требуется конфигурация .well-known на сервере;</li>
|
||||
<li xml:lang="sl">NeoChat za delovanje ne potrebuje konfiguracije .well-know na strežniku.</li>
|
||||
<li>Edited messages won't show up duplicated anymore.</li>
|
||||
<li xml:lang="ar">لن تظهر الرسائل المحررة مكررة بعد الآن.</li>
|
||||
<li xml:lang="ca">Els missatges editats ja no es mostraran duplicats.</li>
|
||||
<li xml:lang="ca-valencia">Els missatges editats ja no es mostraran duplicats.</li>
|
||||
<li xml:lang="ka">ჩასწორებული შეტყობინებები გამეორებულად აღარ გამოჩნდება.</li>
|
||||
<li xml:lang="pt-BR">As mensagens editadas não aparecerão mais duplicadas.</li>
|
||||
<li xml:lang="ru">Редактируемые сообщения больше не отображаются как дубликаты;</li>
|
||||
<li xml:lang="sl">Urejena sporočila se ne bodo več prikazovala kot podvojena.</li>
|
||||
<li>Various graphic glitches have been fixed.</li>
|
||||
<li xml:lang="ar">أُصلحت مشكلات رسومية متنوعة.</li>
|
||||
<li xml:lang="ca">S'han corregit diversos errors gràfics.</li>
|
||||
<li xml:lang="ca-valencia">S'han corregit diversos errors gràfics.</li>
|
||||
<li xml:lang="ka">გასწორდა სხვადასხვა გრაფიკული შეცდომა.</li>
|
||||
<li xml:lang="pt-BR">Diversos problemas gráficos foram corrigidos.</li>
|
||||
<li xml:lang="ru">Исправлены различные графические дефекты;</li>
|
||||
<li xml:lang="sl">Odpravljene so bile različne grafične napake.</li>
|
||||
<li>NeoChat now ask for consent to terms and conditions if required instead of displaying nothing.</li>
|
||||
<li xml:lang="ar">يطلب نيوتشات الآن الموافقة على الشروط والأحكام إذا كانت مطلوبة بدلاً من عدم عرض شيء.</li>
|
||||
<li xml:lang="ca">El NeoChat ara demana consentiment als termes i condicions si es requereixen en lloc de no mostrar res.</li>
|
||||
<li xml:lang="ca-valencia">NeoChat ara demana consentiment als termes i condicions si es requerixen en lloc de no mostrar res.</li>
|
||||
<li xml:lang="ka">NeoChat ახლა გკითხავთ, ეთანხმებით თუ არა გამოყენების პირობებს, თუ ეს აუცილებელია, არაფრის ჩვენების მაგიერ.</li>
|
||||
<li xml:lang="pt-BR">O NeoChat agora solicita a aceitação dos termos e condições, se necessário, em vez de não exibir nada.</li>
|
||||
<li xml:lang="ru">В NeoChat реализован запрос согласия с условиями использования, если это требуется;</li>
|
||||
<li xml:lang="sl">NeoChat zdaj po potrebi zahteva soglasje s pogoji uporabe, namesto da ne prikaže ničesar.</li>
|
||||
<li>Users avatar in the room list are now displayed correctly.</li>
|
||||
<li xml:lang="ar">تُعرض صور المستخدمين الرمزية في قائمة الغرف بشكل صحيح الآن.</li>
|
||||
<li xml:lang="ca">Els avatars d'usuaris a la llista de sales ara es mostren correctament.</li>
|
||||
<li xml:lang="ca-valencia">Els avatars d'usuaris a la llista de sales ara es mostren correctament.</li>
|
||||
<li xml:lang="ka">მომხმარებლის ავატარი ოთახების სიაში ახლა სწორადაა ნაჩვენები.</li>
|
||||
<li xml:lang="pt-BR">Os avatares dos usuários na lista de salas agora são exibidos corretamente.</li>
|
||||
<li xml:lang="ru">Аватары пользователей в списке комнат отображаются корректно;</li>
|
||||
<li xml:lang="sl">Uporabnikovi avatarji v seznamu sob so zdaj pravilno prikazani.</li>
|
||||
<li>Fix image saving</li>
|
||||
<li xml:lang="ar">إصلاح حفظ الصور</li>
|
||||
<li xml:lang="ca">Corregeix el desament de les imatges</li>
|
||||
<li xml:lang="ca-valencia">Corregix la guardada de les imatges</li>
|
||||
<li xml:lang="ka">გასწორდა სურათების შენახვა</li>
|
||||
<li xml:lang="pt-BR">Corrigido salvamento de imagem</li>
|
||||
<li xml:lang="ru">Исправлено сохранение изображений;</li>
|
||||
<li xml:lang="sl">Popravi shranjevanje slike</li>
|
||||
</ul>
|
||||
</description>
|
||||
<url>https://carlschwan.eu/2020/01/13/neochat-1.0.1/</url>
|
||||
@@ -1189,13 +670,6 @@
|
||||
<release version="1.0" date="2020-12-23">
|
||||
<description>
|
||||
<p>Initial release of NeoChat, the KDE matrix client.</p>
|
||||
<p xml:lang="ar">الإصدار الأولي لنيوتشات، عميل ماتركس لكيدي.</p>
|
||||
<p xml:lang="ca">Versió inicial de NeoChat, el client de Matrix de KDE.</p>
|
||||
<p xml:lang="ca-valencia">Versió inicial de NeoChat, el client de Matrix de KDE.</p>
|
||||
<p xml:lang="ka">NeoChat-ის, KDE-ის მატრიცის კლიენტის პირველი ვერსია.</p>
|
||||
<p xml:lang="pt-BR">Lançamento inicial do NeoChat, o cliente Matrix do KDE.</p>
|
||||
<p xml:lang="ru">Первый выпуск NeoChat — клиента Matrix для KDE.</p>
|
||||
<p xml:lang="sl">Začetna izdaja NeoChata, odjemalca matrixa za KDE.</p>
|
||||
</description>
|
||||
<url>https://carlschwan.eu/2020/12/23/announcing-neochat-1.0-the-kde-matrix-client/</url>
|
||||
</release>
|
||||
|
||||
@@ -108,7 +108,6 @@ Comment[ia]=Conversation en ditecto sur Matrix
|
||||
Comment[it]= su Matrix
|
||||
Comment[ka]=ჩატი Matrix-ზე
|
||||
Comment[ko]=Matrix에서 대화하기
|
||||
Comment[lt]=Pokalbiai per Matrix
|
||||
Comment[lv]=Tērzējiet „Matrix“ tīklā
|
||||
Comment[nl]=Chat op Matrix
|
||||
Comment[pl]=Rozmawiaj na Matriksie
|
||||
|
||||
2931
po/ar/neochat.po
2931
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
2582
po/ast/neochat.po
2582
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
3082
po/az/neochat.po
3082
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
2920
po/ca/neochat.po
2920
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2799
po/cs/neochat.po
2799
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
2929
po/da/neochat.po
2929
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
3771
po/de/neochat.po
3771
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
3174
po/el/neochat.po
3174
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
3199
po/en_GB/neochat.po
3199
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
3230
po/eo/neochat.po
3230
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
2718
po/es/neochat.po
2718
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
3110
po/eu/neochat.po
3110
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
3413
po/fi/neochat.po
3413
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
3030
po/fr/neochat.po
3030
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
7506
po/ga/neochat.po
7506
po/ga/neochat.po
File diff suppressed because it is too large
Load Diff
3171
po/gl/neochat.po
3171
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
2934
po/he/neochat.po
2934
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
3231
po/hi/neochat.po
3231
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
3216
po/hu/neochat.po
3216
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
3451
po/ia/neochat.po
3451
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
3153
po/id/neochat.po
3153
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
3075
po/ie/neochat.po
3075
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
3004
po/it/neochat.po
3004
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
2580
po/ja/neochat.po
2580
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
2931
po/ka/neochat.po
2931
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
3162
po/ko/neochat.po
3162
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
3079
po/lt/neochat.po
3079
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
3130
po/lv/neochat.po
3130
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
2937
po/nl/neochat.po
2937
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
2961
po/nn/neochat.po
2961
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
3075
po/pa/neochat.po
3075
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
3025
po/pl/neochat.po
3025
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
3132
po/pt/neochat.po
3132
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
2942
po/pt_BR/neochat.po
2942
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
2963
po/ro/neochat.po
2963
po/ro/neochat.po
File diff suppressed because it is too large
Load Diff
3779
po/ru/neochat.po
3779
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
3237
po/sa/neochat.po
3237
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
3092
po/sk/neochat.po
3092
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
2900
po/sl/neochat.po
2900
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
3171
po/sv/neochat.po
3171
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
3166
po/ta/neochat.po
3166
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
2932
po/tok/neochat.po
2932
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
>carl@carlschwan.eu</email
|
||||
></author>
|
||||
<date
|
||||
>2022‒11‒01</date>
|
||||
>2022-11-01</date>
|
||||
<releaseinfo
|
||||
>22.09</releaseinfo>
|
||||
<productname
|
||||
@@ -111,9 +111,9 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
><title
|
||||
>Telif Hakkı</title>
|
||||
<para
|
||||
>Telif hakkı © 2020–2022 Tobias Fella </para>
|
||||
>Telif hakkı © 2020-2022 Tobias Fella </para>
|
||||
<para
|
||||
>Telif hakkı © 2020–2022 Carl Schwan </para>
|
||||
>Telif hakkı © 2020-2022 Carl Schwan </para>
|
||||
<para
|
||||
>Lisans: GNU Genel Kamu Lisansa, 3. sürüm veya sonrası <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
||||
|
||||
2913
po/tr/neochat.po
2913
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
2942
po/uk/neochat.po
2942
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
2653
po/zh_CN/neochat.po
2653
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
3026
po/zh_TW/neochat.po
3026
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
qt_add_library(neochat STATIC
|
||||
add_library(neochat STATIC
|
||||
controller.cpp
|
||||
controller.h
|
||||
roommanager.cpp
|
||||
@@ -35,10 +35,6 @@ qt_add_library(neochat STATIC
|
||||
models/commonroomsmodel.h
|
||||
texttospeechhelper.h
|
||||
texttospeechhelper.cpp
|
||||
models/limitermodel.cpp
|
||||
models/limitermodel.h
|
||||
supportcontroller.cpp
|
||||
supportcontroller.h
|
||||
)
|
||||
|
||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||
@@ -70,6 +66,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/AttachmentPane.qml
|
||||
qml/QuickFormatBar.qml
|
||||
qml/UserDetailDialog.qml
|
||||
qml/OpenFileDialog.qml
|
||||
qml/KeyVerificationDialog.qml
|
||||
qml/ConfirmLogoutDialog.qml
|
||||
qml/VerificationMessage.qml
|
||||
@@ -78,6 +75,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/EmojiSas.qml
|
||||
qml/VerificationCanceled.qml
|
||||
qml/MessageSourceSheet.qml
|
||||
qml/LocationChooser.qml
|
||||
qml/InvitationView.qml
|
||||
qml/AvatarTabButton.qml
|
||||
qml/OsmLocationPlugin.qml
|
||||
@@ -103,10 +101,9 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/HoverLinkIndicator.qml
|
||||
qml/AvatarNotification.qml
|
||||
qml/ReasonDialog.qml
|
||||
qml/NewPollDialog.qml
|
||||
qml/UserMenu.qml
|
||||
qml/MeetingDialog.qml
|
||||
qml/SeenByDialog.qml
|
||||
qml/SupportDialog.qml
|
||||
DEPENDENCIES
|
||||
QtCore
|
||||
QtQuick
|
||||
@@ -143,17 +140,10 @@ if(WIN32)
|
||||
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
|
||||
endif()
|
||||
|
||||
qt_add_executable(neochat-app
|
||||
add_executable(neochat-app
|
||||
main.cpp
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
set_target_properties(neochat-app PROPERTIES
|
||||
OUTPUT_NAME "neochat-app"
|
||||
PREFIX "lib"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(TARGET Qt::WebView)
|
||||
target_link_libraries(neochat-app PUBLIC Qt::WebView)
|
||||
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
|
||||
@@ -163,7 +153,6 @@ target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
|
||||
|
||||
target_link_libraries(neochat-app PRIVATE
|
||||
neochat
|
||||
KF6::IconThemes
|
||||
)
|
||||
|
||||
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
||||
@@ -194,7 +183,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
|
||||
target_link_libraries(neochat PRIVATE neochatplugin Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
|
||||
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
|
||||
target_link_libraries(neochat PUBLIC
|
||||
LibNeoChat
|
||||
Timeline
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include <KWindowSystem>
|
||||
#endif
|
||||
|
||||
#include <KIconTheme>
|
||||
#include <KLocalizedQmlContext>
|
||||
#include <KLocalizedString>
|
||||
#include <KirigamiApp>
|
||||
@@ -103,10 +102,6 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
|
||||
// We currently need to do this ourselves,
|
||||
// KirigamiApp currently called this after constructing the app which breaks icons on Windows.
|
||||
KIconTheme::initTheme();
|
||||
|
||||
#ifdef HAVE_WEBVIEW
|
||||
QtWebView::initialize();
|
||||
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "jobs/neochatgetcommonroomsjob.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -40,22 +39,8 @@ void CommonRoomsModel::setUserId(const QString &userId)
|
||||
|
||||
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
|
||||
{
|
||||
auto roomId = m_commonRooms[index.row()];
|
||||
auto room = connection()->room(roomId);
|
||||
if (!room) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (roleName) {
|
||||
case Qt::DisplayRole:
|
||||
case RoomNameRole:
|
||||
return room->displayName();
|
||||
case RoomAvatarRole:
|
||||
return room->avatarUrl();
|
||||
case RoomIdRole:
|
||||
return roomId;
|
||||
}
|
||||
|
||||
Q_UNUSED(index)
|
||||
Q_UNUSED(roleName)
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -65,15 +50,6 @@ int CommonRoomsModel::rowCount(const QModelIndex &parent) const
|
||||
return m_commonRooms.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> CommonRoomsModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{RoomIdRole, "roomId"},
|
||||
{RoomNameRole, "roomName"},
|
||||
{RoomAvatarRole, "roomAvatar"},
|
||||
};
|
||||
}
|
||||
|
||||
void CommonRoomsModel::reload()
|
||||
{
|
||||
if (!m_connection || m_userId.isEmpty()) {
|
||||
|
||||
@@ -24,9 +24,7 @@ class CommonRoomsModel : public QAbstractListModel
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
RoomIdRole = Qt::UserRole,
|
||||
RoomNameRole,
|
||||
RoomAvatarRole,
|
||||
RoomIdRole = Qt::DisplayRole,
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
@@ -41,8 +39,6 @@ public:
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
|
||||
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void userIdChanged();
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "models/limitermodel.h"
|
||||
|
||||
LimiterModel::LimiterModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
connect(this, &QSortFilterProxyModel::rowsInserted, this, &LimiterModel::extraCountChanged);
|
||||
connect(this, &QSortFilterProxyModel::rowsRemoved, this, &LimiterModel::extraCountChanged);
|
||||
connect(this, &QSortFilterProxyModel::modelReset, this, &LimiterModel::extraCountChanged);
|
||||
}
|
||||
|
||||
int LimiterModel::maximumCount() const
|
||||
{
|
||||
return m_maximumCount;
|
||||
}
|
||||
|
||||
void LimiterModel::setMaximumCount(int maximumCount)
|
||||
{
|
||||
if (m_maximumCount != maximumCount) {
|
||||
m_maximumCount = maximumCount;
|
||||
Q_EMIT maximumCountChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int LimiterModel::extraCount() const
|
||||
{
|
||||
if (sourceModel()) {
|
||||
return std::max(sourceModel()->rowCount() - maximumCount(), 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool LimiterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent)
|
||||
return source_row < maximumCount();
|
||||
}
|
||||
|
||||
#include "moc_limitermodel.cpp"
|
||||
@@ -1,41 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQmlEngine>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class LimiterModel
|
||||
*
|
||||
* @brief Takes a source QAbstractItemModel model and only displays a desired maximum amount.
|
||||
*
|
||||
* Also gives you the remaining (filtered out) items, useful for sticking in a label or somesuch.
|
||||
*/
|
||||
class LimiterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged)
|
||||
Q_PROPERTY(int extraCount READ extraCount NOTIFY extraCountChanged)
|
||||
|
||||
public:
|
||||
explicit LimiterModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] int maximumCount() const;
|
||||
void setMaximumCount(int maximumCount);
|
||||
|
||||
[[nodiscard]] int extraCount() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void maximumCountChanged();
|
||||
void extraCountChanged();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
private:
|
||||
int m_maximumCount = 0;
|
||||
};
|
||||
@@ -92,9 +92,7 @@ void NotificationsModel::setConnection(NeoChatConnection *connection)
|
||||
|
||||
void NotificationsModel::loadData()
|
||||
{
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(m_connection);
|
||||
if (m_job || (m_notifications.size() && m_nextToken.isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -287,7 +287,6 @@ Name[ia]=Comparti
|
||||
Name[it]=Condivisione
|
||||
Name[ka]=გაზიარება
|
||||
Name[ko]=공유
|
||||
Name[lt]=Bendrinti
|
||||
Name[lv]=Kopīgot
|
||||
Name[nl]=Gedeelde
|
||||
Name[nn]=Del
|
||||
@@ -323,7 +322,6 @@ Comment[ia]=Le exito de compartir un pecietta de contento
|
||||
Comment[it]=Il risultato della condivisione di un contenuto
|
||||
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
|
||||
Comment[ko]=콘텐츠 공유 결과
|
||||
Comment[lt]=Turinio dalies bendrinimo rezultatas
|
||||
Comment[lv]=Satura kopīgošanas rezultāts
|
||||
Comment[nl]=Het resultaat van het delen van een stukje inhoud
|
||||
Comment[nn]=Resultatet av deling av innhald
|
||||
|
||||
@@ -207,6 +207,10 @@
|
||||
</entry>
|
||||
</group>
|
||||
<group name="FeatureFlags">
|
||||
<entry name="Threads" type="bool">
|
||||
<label>Enable threads</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="Phone3PId" type="bool">
|
||||
<label>Enable add phone numbers as 3PIDs</label>
|
||||
<default>false</default>
|
||||
|
||||
@@ -38,7 +38,7 @@ NotificationsManager::NotificationsManager(QObject *parent)
|
||||
{
|
||||
}
|
||||
|
||||
void NotificationsManager::handleNotifications(const QPointer<NeoChatConnection> &connection)
|
||||
void NotificationsManager::handleNotifications(QPointer<NeoChatConnection> connection)
|
||||
{
|
||||
if (KNotificationPermission::checkPermission() == Qt::PermissionStatus::Granted) {
|
||||
startNotificationJob(connection);
|
||||
@@ -68,7 +68,7 @@ void NotificationsManager::startNotificationJob(QPointer<NeoChatConnection> conn
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::processNotificationJob(const QPointer<NeoChatConnection> &connection, const GetNotificationsJob *job, const bool initialization)
|
||||
void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization)
|
||||
{
|
||||
if (!job || !connection || !connection->isLoggedIn()) {
|
||||
return;
|
||||
@@ -82,7 +82,8 @@ void NotificationsManager::processNotificationJob(const QPointer<NeoChatConnecti
|
||||
if (!m_initialTimestamp.contains(connectionId)) {
|
||||
m_initialTimestamp[connectionId] = notification["ts"_L1].toVariant().toLongLong();
|
||||
} else {
|
||||
if (const auto timestamp = notification["ts"_L1].toVariant().toLongLong(); timestamp > m_initialTimestamp[connectionId]) {
|
||||
qint64 timestamp = notification["ts"_L1].toVariant().toLongLong();
|
||||
if (timestamp > m_initialTimestamp[connectionId]) {
|
||||
m_initialTimestamp[connectionId] = timestamp;
|
||||
}
|
||||
}
|
||||
@@ -159,29 +160,29 @@ void NotificationsManager::processNotificationJob(const QPointer<NeoChatConnecti
|
||||
}
|
||||
}
|
||||
|
||||
bool NotificationsManager::shouldPostNotification(const QPointer<NeoChatConnection> &connection, const QJsonValue ¬ification)
|
||||
bool NotificationsManager::shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue ¬ification)
|
||||
{
|
||||
if (connection == nullptr || !connection->isLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto room = connection->room(notification["room_id"_L1].toString());
|
||||
auto room = connection->room(notification["room_id"_L1].toString());
|
||||
if (room == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the room is the current room and the application is active, the notification
|
||||
// If the room is the current room and the application is active the notification
|
||||
// should not be shown.
|
||||
// This is set up so that if the application is inactive, the notification will
|
||||
// This is setup so that if the application is inactive the notification will
|
||||
// always be posted, even if the room is the current room.
|
||||
if (RoomManager::instance().currentRoom() && room->id() == RoomManager::instance().currentRoom()->id()
|
||||
&& QGuiApplication::applicationState() == Qt::ApplicationActive) {
|
||||
bool isCurrentRoom = RoomManager::instance().currentRoom() && room->id() == RoomManager::instance().currentRoom()->id();
|
||||
if (isCurrentRoom && QGuiApplication::applicationState() == Qt::ApplicationActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the notification timestamp is earlier than the initial timestamp, assume
|
||||
// If the notification timestamp is earlier than the initial timestamp assume
|
||||
// the notification is old and shouldn't be posted.
|
||||
const auto timestamp = notification["ts"_L1].toDouble();
|
||||
qint64 timestamp = notification["ts"_L1].toDouble();
|
||||
if (timestamp < m_initialTimestamp[connection->user()->id()]) {
|
||||
return false;
|
||||
}
|
||||
@@ -198,7 +199,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
const QString &text,
|
||||
const QImage &icon,
|
||||
const QString &replyEventId,
|
||||
const bool canReply,
|
||||
bool canReply,
|
||||
qint64 timestamp)
|
||||
{
|
||||
const QString roomId = room->id();
|
||||
@@ -215,12 +216,12 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
}
|
||||
});
|
||||
|
||||
notification->setTitle(room->displayName());
|
||||
|
||||
QString entry;
|
||||
if (room->isDirectChat()) {
|
||||
if (sender == room->displayName()) {
|
||||
notification->setTitle(sender);
|
||||
entry = text.toHtmlEscaped();
|
||||
} else {
|
||||
notification->setTitle(room->displayName());
|
||||
entry = i18n("%1: %2", sender, text.toHtmlEscaped());
|
||||
}
|
||||
|
||||
@@ -252,9 +253,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
notification->setReplyAction(std::move(replyAction));
|
||||
}
|
||||
|
||||
if (Controller::instance().accounts()->rowCount() > 1) {
|
||||
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
|
||||
}
|
||||
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
|
||||
notification->sendEvent();
|
||||
}
|
||||
|
||||
@@ -270,8 +269,10 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom)
|
||||
if (NeoChatConfig::rejectUnknownInvites()) {
|
||||
auto job = room->connection()->callApi<NeochatGetCommonRoomsJob>(roomMemberEvent->senderId());
|
||||
connect(job, &BaseJob::result, this, [this, job, room] {
|
||||
if (QJsonObject replyData = job->jsonData(); replyData.contains(u"joined"_s)) {
|
||||
if (!replyData["joined"_L1].toArray().isEmpty()) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
if (replyData.contains(u"joined"_s)) {
|
||||
const bool inAnyOfOurRooms = !replyData["joined"_L1].toArray().isEmpty();
|
||||
if (inAnyOfOurRooms) {
|
||||
doPostInviteNotification(room);
|
||||
} else {
|
||||
room->forget();
|
||||
@@ -283,7 +284,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom)
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::doPostInviteNotification(const QPointer<NeoChatRoom> &room)
|
||||
void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
|
||||
{
|
||||
const auto roomMemberEvent = room->currentState().get<RoomMemberEvent>(room->localMember().id());
|
||||
if (roomMemberEvent == nullptr) {
|
||||
@@ -292,18 +293,18 @@ void NotificationsManager::doPostInviteNotification(const QPointer<NeoChatRoom>
|
||||
const auto sender = room->member(roomMemberEvent->senderId());
|
||||
|
||||
QImage avatar_image;
|
||||
if (!room->member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
|
||||
if (roomMemberEvent && !room->member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
|
||||
avatar_image = room->member(roomMemberEvent->senderId()).avatar(128, 128, {});
|
||||
} else {
|
||||
qWarning() << "using this room's avatar";
|
||||
avatar_image = room->avatar(128);
|
||||
}
|
||||
|
||||
const auto notification = new KNotification(u"invite"_s);
|
||||
KNotification *notification = new KNotification(u"invite"_s);
|
||||
notification->setText(i18n("%1 invited you to a room", sender.htmlSafeDisplayName()));
|
||||
notification->setTitle(room->displayName());
|
||||
notification->setPixmap(createNotificationImage(avatar_image, nullptr));
|
||||
const auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
|
||||
auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
|
||||
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
|
||||
if (!room) {
|
||||
return;
|
||||
@@ -346,9 +347,7 @@ void NotificationsManager::doPostInviteNotification(const QPointer<NeoChatRoom>
|
||||
m_invitations.remove(room->id());
|
||||
});
|
||||
|
||||
if (Controller::instance().accounts()->rowCount() > 1) {
|
||||
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
|
||||
}
|
||||
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
|
||||
|
||||
notification->sendEvent();
|
||||
}
|
||||
@@ -364,9 +363,11 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
|
||||
{
|
||||
const auto json = QJsonDocument::fromJson(message).object();
|
||||
|
||||
const auto type = json["notification"_L1]["type"_L1].toString();
|
||||
|
||||
// the only two types of push notifications we support right now
|
||||
if (const auto type = json["notification"_L1]["type"_L1].toString(); type == u"m.room.message"_s || type == u"m.room.encrypted"_s) {
|
||||
const auto notification = new KNotification("message"_L1);
|
||||
if (type == u"m.room.message"_s || type == u"m.room.encrypted"_s) {
|
||||
auto notification = new KNotification("message"_L1);
|
||||
|
||||
const auto sender = json["notification"_L1]["sender_display_name"_L1].toString();
|
||||
const auto roomName = json["notification"_L1]["room_name"_L1].toString();
|
||||
@@ -386,13 +387,13 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
|
||||
}
|
||||
|
||||
#ifdef HAVE_KIO
|
||||
const auto openAction = notification->addAction(i18n("Open NeoChat"));
|
||||
auto openAction = notification->addAction(i18n("Open NeoChat"));
|
||||
connect(openAction, &KNotificationAction::activated, notification, [=]() {
|
||||
QString properId = roomId;
|
||||
properId = properId.replace(u"#"_s, QString());
|
||||
properId = properId.replace(u"!"_s, QString());
|
||||
|
||||
const auto job = new KIO::ApplicationLauncherJob(KService::serviceByDesktopName(u"org.kde.neochat"_s));
|
||||
auto *job = new KIO::ApplicationLauncherJob(KService::serviceByDesktopName(u"org.kde.neochat"_s));
|
||||
job->setUrls({QUrl::fromUserInput(u"matrix:r/%1"_s.arg(properId))});
|
||||
job->start();
|
||||
});
|
||||
@@ -423,12 +424,13 @@ QPixmap NotificationsManager::createNotificationImage(const QImage &icon, NeoCha
|
||||
painter.setBrush(Qt::white);
|
||||
painter.drawRoundedRect(imageRect, imageRect.width(), imageRect.height());
|
||||
|
||||
const QBrush brush(icon.scaledToHeight(biggestDimension));
|
||||
QBrush brush(icon.scaledToHeight(biggestDimension));
|
||||
painter.setBrush(brush);
|
||||
painter.drawRoundedRect(imageRect, imageRect.width(), imageRect.height());
|
||||
|
||||
if (room) {
|
||||
if (const auto roomAvatar = room->avatar(imageRect.width(), imageRect.height()); !roomAvatar.isNull() && icon != roomAvatar) {
|
||||
if (room != nullptr) {
|
||||
const QImage roomAvatar = room->avatar(imageRect.width(), imageRect.height());
|
||||
if (!roomAvatar.isNull() && icon != roomAvatar) {
|
||||
const QRect lowerQuarter{imageRect.center(), imageRect.size() / 2};
|
||||
|
||||
painter.setBrush(Qt::white);
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
/**
|
||||
* @brief Handle the notifications for the given connection.
|
||||
*/
|
||||
void handleNotifications(const QPointer<NeoChatConnection> &connection);
|
||||
void handleNotifications(QPointer<NeoChatConnection> connection);
|
||||
|
||||
private:
|
||||
QHash<QString, qint64> m_initialTimestamp;
|
||||
@@ -67,8 +67,8 @@ private:
|
||||
QStringList m_connActiveJob;
|
||||
void startNotificationJob(QPointer<NeoChatConnection> connection);
|
||||
|
||||
static QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room);
|
||||
bool shouldPostNotification(const QPointer<NeoChatConnection> &connection, const QJsonValue ¬ification);
|
||||
QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room);
|
||||
bool shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue ¬ification);
|
||||
void postNotification(NeoChatRoom *room,
|
||||
const QString &sender,
|
||||
const QString &text,
|
||||
@@ -77,7 +77,7 @@ private:
|
||||
bool canReply,
|
||||
qint64 timestamp);
|
||||
|
||||
void doPostInviteNotification(const QPointer<NeoChatRoom> &room);
|
||||
void doPostInviteNotification(QPointer<NeoChatRoom> room);
|
||||
|
||||
QHash<QString, std::pair<qint64, KNotification *>> m_notifications;
|
||||
QHash<QString, QPointer<KNotification>> m_invitations;
|
||||
@@ -85,5 +85,5 @@ private:
|
||||
bool permissionAsked = false;
|
||||
|
||||
private Q_SLOTS:
|
||||
void processNotificationJob(const QPointer<NeoChatConnection> &connection, const Quotient::GetNotificationsJob *job, bool initialization);
|
||||
void processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtMultimedia
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||
@@ -19,25 +18,27 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
required property NeoChatConnection connection
|
||||
required property Kirigami.ApplicationWindow window
|
||||
|
||||
data: MediaDevices {
|
||||
id: devices
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button", "Show QR Code")
|
||||
icon.name: "view-barcode-qr-symbolic"
|
||||
onTriggered: {
|
||||
(Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||
text: "https://matrix.to/#/" + root.connection.localUser.id,
|
||||
title: root.connection.localUser.displayName,
|
||||
subtitle: root.connection.localUser.id,
|
||||
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
|
||||
avatarSource: root.connection.localUser.avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(root.connection.localUser.avatarUrl) : ""
|
||||
}) as QrCodeMaximizeComponent).open();
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button", "Open Profile")
|
||||
icon.name: "im-user-symbolic"
|
||||
onTriggered: RoomManager.resolveResource(root.connection.localUserId, "qr") // Use "qr" action to make sure a room isn't passed, see RoomManager::visitUser
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Scan a QR Code")
|
||||
icon.name: "document-scan-symbolic"
|
||||
visible: devices.videoInputs.length > 0
|
||||
onTriggered: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent("org.kde.neochat", "QrScannerPage"), {
|
||||
text: i18nc("@action:inmenu", "Switch Account")
|
||||
icon.name: "system-switch-user"
|
||||
shortcut: "Ctrl+U"
|
||||
onTriggered: (Qt.createComponent("org.kde.neochat", "AccountSwitchDialog").createObject(QQC2.Overlay.overlay, {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Scan a QR Code")
|
||||
})
|
||||
}) as Kirigami.Dialog).open();
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
@@ -54,6 +55,14 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Devices")
|
||||
icon.name: "computer-symbolic"
|
||||
onTriggered: {
|
||||
NeoChatSettingsView.open('devices');
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Open Developer Tools")
|
||||
icon.name: "tools"
|
||||
@@ -67,6 +76,14 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
})
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Open Secret Backup")
|
||||
icon.name: "unlock"
|
||||
onTriggered: root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UnlockSSSSDialog'), {}, {
|
||||
title: i18nc("@title:window", "Open Key Backup")
|
||||
})
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Verify This Device")
|
||||
icon.name: "security-low"
|
||||
@@ -86,25 +103,10 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu Open support dialog", "Support")
|
||||
icon.name: "help-contents-symbolic"
|
||||
onTriggered: {
|
||||
(Qt.createComponent("org.kde.neochat", "SupportDialog").createObject(QQC2.Overlay.overlay, {
|
||||
connection: root.connection,
|
||||
}) as SupportDialog).open();
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
separator: true
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Switch Account")
|
||||
icon.name: "system-switch-user"
|
||||
shortcut: "Ctrl+U"
|
||||
onTriggered: (Qt.createComponent("org.kde.neochat", "AccountSwitchDialog").createObject(QQC2.Overlay.overlay, {
|
||||
text: i18nc("@action:inmenu", "Logout…")
|
||||
icon.name: "im-kick-user"
|
||||
onTriggered: (Qt.createComponent("org.kde.neochat", "ConfirmLogoutDialog").createObject(QQC2.Overlay.overlay, {
|
||||
connection: root.connection
|
||||
}) as Kirigami.Dialog).open();
|
||||
}) as Kirigami.Dialog).open()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@ Kirigami.Dialog {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
root.close();
|
||||
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {}, {
|
||||
title: i18nc("@title:window", "Login")
|
||||
});
|
||||
root.close();
|
||||
}
|
||||
Keys.onUpPressed: {
|
||||
accountView.currentIndex = accountView.count - 1;
|
||||
|
||||
@@ -21,7 +21,6 @@ Delegates.RoundedItemDelegate {
|
||||
signal contextMenuRequested
|
||||
signal selected
|
||||
|
||||
activeFocusOnTab: true
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
QQC2.ToolTip.visible: hovered
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ Components.AbstractMaximizeComponent {
|
||||
property NeochatRoomMember author
|
||||
|
||||
/**
|
||||
* @brief The timestamp of the event as a neoChatDateTime.
|
||||
* @brief The timestamp of the message.
|
||||
*/
|
||||
required property neoChatDateTime dateTime
|
||||
property var time
|
||||
|
||||
/**
|
||||
* @brief The code text to show.
|
||||
@@ -64,7 +64,7 @@ Components.AbstractMaximizeComponent {
|
||||
}
|
||||
QQC2.Label {
|
||||
id: dateTimeLabel
|
||||
text: root.dateTime.relativeDateTime
|
||||
text: root.time.toLocaleString(Qt.locale(), Locale.ShortFormat)
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
@@ -79,7 +79,7 @@ Components.AbstractMaximizeComponent {
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
QQC2.TextArea {
|
||||
id: codeTextEdit
|
||||
id: codeText
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
leftPadding: lineNumberColumn.width + lineNumberColumn.anchors.leftMargin + Kirigami.Units.smallSpacing * 2
|
||||
@@ -100,15 +100,15 @@ Components.AbstractMaximizeComponent {
|
||||
|
||||
SyntaxHighlighter {
|
||||
property string definitionName: Repository.definitionForName(root.language).name
|
||||
textEdit: definitionName == "None" ? null : codeTextEdit
|
||||
textEdit: definitionName == "None" ? null : codeText
|
||||
definition: definitionName
|
||||
}
|
||||
ColumnLayout {
|
||||
id: lineNumberColumn
|
||||
anchors {
|
||||
top: codeTextEdit.top
|
||||
topMargin: codeTextEdit.topPadding + 1
|
||||
left: codeTextEdit.left
|
||||
top: codeText.top
|
||||
topMargin: codeText.topPadding + 1
|
||||
left: codeText.left
|
||||
leftMargin: Kirigami.Units.smallSpacing
|
||||
}
|
||||
spacing: 0
|
||||
@@ -116,7 +116,7 @@ Components.AbstractMaximizeComponent {
|
||||
id: repeater
|
||||
model: LineModel {
|
||||
id: lineModel
|
||||
Component.onCompleted: setDocument(codeTextEdit.textDocument)
|
||||
document: codeText.textDocument
|
||||
}
|
||||
delegate: QQC2.Label {
|
||||
id: label
|
||||
@@ -150,6 +150,4 @@ Components.AbstractMaximizeComponent {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
onOpened: forceActiveFocus()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import QtQml.Models
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
|
||||
@@ -13,21 +13,22 @@ Kirigami.PromptDialog {
|
||||
|
||||
required property NeoChatRoom room
|
||||
|
||||
title: root.room.isSpace ? i18nc("@title:dialog", "Confirm Leaving Space") : i18nc("@title:dialog", "Confirm Leaving Room")
|
||||
title: i18nc("@title:dialog", "Confirm Leaving Room")
|
||||
subtitle: root.room ? i18nc("Do you really want to leave <room name>?", "Do you really want to leave %1?", root.room.displayNameForHtml) : ""
|
||||
dialogType: Kirigami.PromptDialog.Warning
|
||||
standardButtons: QQC2.Dialog.Cancel
|
||||
|
||||
onAccepted: root.room.forget()
|
||||
onRejected: {
|
||||
root.close();
|
||||
}
|
||||
|
||||
footer: QQC2.DialogButtonBox {
|
||||
standardButtons: QQC2.Dialog.Cancel
|
||||
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button Leave this room/space", "Leave")
|
||||
icon.name: "arrow-left-symbolic"
|
||||
|
||||
onClicked: root.accept()
|
||||
|
||||
text: i18nc("@action:button", "Leave Room")
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
icon.name: "arrow-left-symbolic"
|
||||
onClicked: root.room.forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,22 @@ Kirigami.PromptDialog {
|
||||
title: i18nc("@title:dialog", "Sign out")
|
||||
subtitle: i18n("Are you sure you want to sign out?")
|
||||
dialogType: Kirigami.PromptDialog.Warning
|
||||
standardButtons: QQC2.Dialog.Cancel
|
||||
|
||||
onAccepted: root.connection.logout(true)
|
||||
onRejected: {
|
||||
root.close();
|
||||
}
|
||||
|
||||
footer: QQC2.DialogButtonBox {
|
||||
standardButtons: QQC2.Dialog.Cancel
|
||||
|
||||
QQC2.Button {
|
||||
text: i18nc("@action:button", "Sign out")
|
||||
onClicked: root.accept()
|
||||
|
||||
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||
onClicked: {
|
||||
root.connection.logout(true);
|
||||
root.close();
|
||||
root.accepted();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,5 +17,12 @@ Kirigami.PromptDialog {
|
||||
|
||||
standardButtons: QQC2.DialogButtonBox.Open | QQC2.DialogButtonBox.Cancel
|
||||
|
||||
onAccepted: Qt.openUrlExternally(root.link)
|
||||
onAccepted: {
|
||||
Qt.openUrlExternally(root.link);
|
||||
root.close();
|
||||
}
|
||||
|
||||
onRejected: {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ Kirigami.Page {
|
||||
icon.name: "document-edit"
|
||||
onTriggered: {
|
||||
root.room.setRoomState(root.type, root.stateKey, sourceTextArea.text);
|
||||
root.Kirigami.PageStack.closeDialog();
|
||||
root.closeDialog();
|
||||
}
|
||||
enabled: QmlUtils.isValidJson(sourceTextArea.text)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ Kirigami.Page {
|
||||
id: repeater
|
||||
model: LineModel {
|
||||
id: lineModel
|
||||
Component.onCompleted: setDocument(sourceTextArea.textDocument)
|
||||
document: sourceTextArea.textDocument
|
||||
}
|
||||
delegate: QQC2.Label {
|
||||
id: label
|
||||
|
||||
@@ -41,11 +41,13 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
QQC2.Button {
|
||||
anchors.bottom: parent.bottom
|
||||
text: i18n("They match")
|
||||
icon.name: "dialog-ok"
|
||||
onClicked: root.accept()
|
||||
}
|
||||
QQC2.Button {
|
||||
anchors.bottom: parent.bottom
|
||||
text: i18n("They don't match")
|
||||
icon.name: "dialog-cancel"
|
||||
onClicked: root.reject()
|
||||
|
||||
@@ -9,6 +9,8 @@ import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
import org.kde.kirigamiaddons.labs.components as Components
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: root
|
||||
|
||||
|
||||
@@ -45,12 +45,14 @@ Labs.MenuBar {
|
||||
}
|
||||
Labs.MenuItem {
|
||||
icon.name: "compass-symbolic"
|
||||
text: i18nc("@action:inmenu Explore public rooms and spaces", "Explore")
|
||||
text: i18nc("@action:inmenu", "Explore Rooms")
|
||||
enabled: root.connection
|
||||
onTriggered: {
|
||||
let dialog = root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
|
||||
connection: root.connection
|
||||
}, {});
|
||||
}, {
|
||||
title: i18nc("@title", "Explore Rooms")
|
||||
});
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
|
||||
});
|
||||
|
||||
@@ -27,17 +27,16 @@ ColumnLayout {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
KirigamiComponents.AvatarButton {
|
||||
KirigamiComponents.Avatar {
|
||||
id: avatar
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
name: root.invitingMember.displayName
|
||||
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
|
||||
color: root.invitingMember.color
|
||||
|
||||
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -139,24 +138,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: viewProfileDelegate
|
||||
|
||||
icon.name: "user-properties-symbolic"
|
||||
text: i18nc("@action:button View this user's profile", "View %1's Profile", root.invitingMember.displayName)
|
||||
|
||||
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: viewProfileDelegate
|
||||
below: ignoreUserDelegate
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: ignoreUserDelegate
|
||||
|
||||
icon.name: "list-remove-symbolic"
|
||||
text: i18nc("@action:button Ignore the user", "Ignore %1 and Reject Invite", root.invitingMember.displayName)
|
||||
text: i18nc("@action:button Block the user", "Block %1", root.invitingMember.displayName)
|
||||
|
||||
onClicked: {
|
||||
root.currentRoom.forget()
|
||||
@@ -166,7 +149,7 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: (root.currentRoom.connection as NeoChatConnection).canCheckMutualRooms
|
||||
visible: root.currentRoom.connection.canCheckMutualRooms
|
||||
spacing: 0
|
||||
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing * 2
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
@@ -10,6 +8,7 @@ import QtQuick.Window
|
||||
import QtQml
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Page {
|
||||
@@ -24,63 +23,72 @@ Kirigami.Page {
|
||||
name: "cancelled"
|
||||
when: root.session.state === KeyVerificationSession.CANCELED
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: verificationCanceled
|
||||
target: stateLoader
|
||||
sourceComponent: verificationCanceled
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForVerification"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: emojiSas
|
||||
target: stateLoader
|
||||
sourceComponent: emojiSas
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForReady"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORREADY
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "incoming"
|
||||
when: root.session.state === KeyVerificationSession.INCOMING
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForKey"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORKEY
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForAccept"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORACCEPT
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForMac"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "ready"
|
||||
when: root.session.state === KeyVerificationSession.READY
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: chooseVerificationComponent
|
||||
target: stateLoader
|
||||
sourceComponent: chooseVerificationComponent
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "done"
|
||||
when: root.session.state === KeyVerificationSession.DONE
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtLocation
|
||||
import QtPositioning
|
||||
|
||||
@@ -43,8 +45,6 @@ Components.AbstractMaximizeComponent {
|
||||
}
|
||||
]
|
||||
|
||||
onOpened: forceActiveFocus()
|
||||
|
||||
PositionSource {
|
||||
id: positionSource
|
||||
|
||||
@@ -100,7 +100,7 @@ Kirigami.ApplicationWindow {
|
||||
|
||||
function onCurrentRoomChanged() {
|
||||
if (RoomManager.currentRoom && root.pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
|
||||
let roomPage = root.pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
|
||||
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
|
||||
roomPage.forceActiveFocus();
|
||||
roomPage.backRequested.connect(event => {
|
||||
RoomManager.clearCurrentRoom();
|
||||
@@ -151,6 +151,8 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
|
||||
contextDrawer: RoomDrawer {
|
||||
id: contextDrawer
|
||||
|
||||
// This is a memory for all user initiated actions on the drawer, i.e. clicking the button
|
||||
// It is used to ensure that user choice is remembered when changing pages and expanding and contracting the window width
|
||||
property bool drawerUserState: NeoChatConfig.autoRoomInfoDrawer
|
||||
@@ -176,9 +178,9 @@ Kirigami.ApplicationWindow {
|
||||
|
||||
// Connect to the onClicked function of the RoomDrawer handle button
|
||||
Connections {
|
||||
target: root.contextDrawer.handle.children[0]
|
||||
target: contextDrawer.handle.children[0]
|
||||
function onClicked() {
|
||||
root.contextDrawer.drawerUserState = root.contextDrawer.drawerOpen;
|
||||
contextDrawer.drawerUserState = contextDrawer.drawerOpen;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user