Compare commits

..

1 Commits

Author SHA1 Message Date
Joshua Goins
8e0f0182cb Add Prison dependency to QML modules so Prison is deployed on Windows
Otherwise we get an error because the DLL isn't found.
2025-06-01 04:03:25 +02:00
217 changed files with 25732 additions and 30448 deletions

View File

@@ -2,5 +2,7 @@
; SPDX-License-Identifier: CC0-1.0 ; SPDX-License-Identifier: CC0-1.0
[BlueprintSettings] [BlueprintSettings]
kde/frameworks/extra-cmake-modules.version=master
kde/unreleased/kirigami-addons.version=master
kde/applications/neochat.packageAppx=True kde/applications/neochat.packageAppx=True
libs/qt.qtMajorVersion=6 libs/qt.qtMajorVersion=6

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat", "id": "org.kde.neochat",
"branch": "master", "branch": "master",
"runtime": "org.kde.Platform", "runtime": "org.kde.Platform",
"runtime-version": "6.9", "runtime-version": "6.8",
"sdk": "org.kde.Sdk", "sdk": "org.kde.Sdk",
"command": "neochat", "command": "neochat",
"tags": [ "tags": [
@@ -149,6 +149,27 @@
], ],
"builddir": true "builddir": true
}, },
{
"name": "qcoro",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DQCORO_BUILD_EXAMPLES=OFF",
"-DBUILD_TESTING=OFF"
],
"sources": [
{
"type": "archive",
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.11.0.tar.gz",
"sha256": "9942c5b4c533192f6c5954dc6d10178b3829075e6a621b67df73f0a4b74d8297",
"x-checker-data": {
"type": "anitya",
"project-id": 236236,
"stable-only": true,
"url-template": "https://github.com/danvratil/qcoro/archive/refs/tags/v$version.tar.gz"
}
}
]
},
{ {
"name": "kunifiedpush", "name": "kunifiedpush",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",

View File

@@ -3,20 +3,19 @@
include: include:
- project: sysadmin/ci-utilities - project: sysadmin/ci-utilities
ref: work/switch-vm-ci
file: file:
#- /gitlab-templates/reuse-lint.yml - /gitlab-templates/reuse-lint.yml
#- /gitlab-templates/json-validation.yml - /gitlab-templates/json-validation.yml
#- /gitlab-templates/xml-lint.yml - /gitlab-templates/xml-lint.yml
#- /gitlab-templates/yaml-lint.yml - /gitlab-templates/yaml-lint.yml
#- /gitlab-templates/android-qt6.yml - /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml - /gitlab-templates/linux-qt6.yml
- /gitlab-templates/linux-qt6-next.yml - /gitlab-templates/linux-qt6-next.yml
#- /gitlab-templates/windows-qt6.yml - /gitlab-templates/windows-qt6.yml
#- /gitlab-templates/freebsd-qt6.yml - /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml - /gitlab-templates/flatpak.yml
- /gitlab-templates/snap-snapcraft-lxd.yml - /gitlab-templates/snap-snapcraft-lxd.yml
#- /gitlab-templates/craft-android-qt6-apks.yml - /gitlab-templates/craft-android-qt6-apks.yml
#- /gitlab-templates/craft-appimage-qt6.yml - /gitlab-templates/craft-appimage-qt6.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml - /gitlab-templates/craft-windows-x86-64-qt6.yml
- /gitlab-templates/craft-windows-appx-qt6.yml - /gitlab-templates/craft-windows-appx-qt6.yml

View File

@@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script. # KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "25") set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "11") set(RELEASE_SERVICE_VERSION_MINOR "07")
set(RELEASE_SERVICE_VERSION_MICRO "70") set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")

View File

@@ -61,7 +61,6 @@ private Q_SLOTS:
void receiveRichStrikethrough(); void receiveRichStrikethrough();
void receiveRichtextIn(); void receiveRichtextIn();
void receiveRichMxcUrl(); void receiveRichMxcUrl();
void receiveRichPlainUrl_data();
void receiveRichPlainUrl(); void receiveRichPlainUrl();
void receiveRichEdited_data(); void receiveRichEdited_data();
void receiveRichEdited(); void receiveRichEdited();
@@ -451,32 +450,6 @@ void TextHandlerTest::receiveRichMxcUrl()
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
} }
void TextHandlerTest::receiveRichPlainUrl_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
// This is an actual link that caused trouble which is why it's so long. Keeping
// so we can confirm consistent behaviour for complex urls.
QTest::addRow("link 1")
<< u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s
<< u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was broken into 3 but needs to
// be just single link.
QTest::addRow("link 2")
<< u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s
<< u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
QTest::addRow("email") << uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
<< uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
QTest::addRow("mxid")
<< u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s
<< u"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>"_s;
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s << u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
}
/** /**
* For when your rich input string has a plain text url left in. * For when your rich input string has a plain text url left in.
* *
@@ -485,13 +458,46 @@ void TextHandlerTest::receiveRichPlainUrl_data()
*/ */
void TextHandlerTest::receiveRichPlainUrl() void TextHandlerTest::receiveRichPlainUrl()
{ {
QFETCH(QString, input); // This is an actual link that caused trouble which is why it's so long. Keeping
QFETCH(QString, output); // so we can confirm consistent behaviour for complex urls.
const QString testInputStringLink1 =
u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
const QString testOutputStringLink1 =
u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&amp;via=matrix.org&amp;via=fedora.im\">Link already rich</a>"_s;
// Another real case. The linkification wasn't handling it when a single link
// contains what looks like and email. It was been broken into 3 but needs to
// be just single link.
const QString testInputStringLink2 = u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s;
const QString testOutputStringLink2 =
u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
QString testInputStringEmail = uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s;
QString testOutputStringEmail = uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
QString testInputStringMxId = u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s;
QString testOutputStringMxId =
u"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>"_s;
QString testInputStringMxIdWithPrefix = u"a @user:kde.org b"_s;
QString testOutputStringMxIdWithPrefix = u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(input); testTextHandler.setData(testInputStringLink1);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), output); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink1);
testTextHandler.setData(testInputStringLink2);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringLink2);
testTextHandler.setData(testInputStringEmail);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringEmail);
testTextHandler.setData(testInputStringMxId);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
testTextHandler.setData(testInputStringMxIdWithPrefix);
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxIdWithPrefix);
} }
void TextHandlerTest::receiveRichEdited_data() void TextHandlerTest::receiveRichEdited_data()

View File

@@ -57,7 +57,7 @@ void TimelineMessageModelTest::switchEmptyRoom()
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s); auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s); auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s);
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *))); QSignalSpy spy(model, SIGNAL(roomChanged()));
QCOMPARE(model->room(), nullptr); QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom); model->setRoom(firstRoom);
@@ -77,7 +77,7 @@ void TimelineMessageModelTest::switchSyncedRoom()
auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s); auto firstRoom = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s); auto secondRoom = new TestUtils::TestRoom(connection, u"#secondRoom:kde.org"_s, u"test-messageventmodel-sync.json"_s);
QSignalSpy spy(model, SIGNAL(roomChanged(NeoChatRoom *, NeoChatRoom *))); QSignalSpy spy(model, SIGNAL(roomChanged()));
QCOMPARE(model->room(), nullptr); QCOMPARE(model->room(), nullptr);
model->setRoom(firstRoom); model->setRoom(firstRoom);
@@ -208,7 +208,7 @@ void TimelineMessageModelTest::idToRow()
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s); auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
model->setRoom(room); model->setRoom(room);
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0); QCOMPARE(model->eventIdToRow(u"$153456789:example.org"_s), 0);
} }
void TimelineMessageModelTest::cleanup() void TimelineMessageModelTest::cleanup()

View File

@@ -477,8 +477,6 @@
<content_attribute id="social-chat">intense</content_attribute> <content_attribute id="social-chat">intense</content_attribute>
</content_rating> </content_rating>
<releases> <releases>
<release version="25.04.3" date="2025-07-03"/>
<release version="25.04.2" date="2025-06-05"/>
<release version="25.04.1" date="2025-05-08"/> <release version="25.04.1" date="2025-05-08"/>
<release version="25.04.0" date="2025-04-17"/> <release version="25.04.0" date="2025-04-17"/>
<release version="24.12.3" date="2025-03-06"/> <release version="24.12.3" date="2025-03-06"/>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,6 @@ add_subdirectory(libneochat)
add_subdirectory(login) add_subdirectory(login)
add_subdirectory(rooms) add_subdirectory(rooms)
add_subdirectory(roominfo) add_subdirectory(roominfo)
add_subdirectory(messagecontent)
add_subdirectory(timeline) add_subdirectory(timeline)
add_subdirectory(spaces) add_subdirectory(spaces)
add_subdirectory(chatbar) add_subdirectory(chatbar)

View File

@@ -20,6 +20,8 @@ add_library(neochat STATIC
windowcontroller.h windowcontroller.h
models/serverlistmodel.cpp models/serverlistmodel.cpp
models/serverlistmodel.h models/serverlistmodel.h
logger.cpp
logger.h
models/notificationsmodel.cpp models/notificationsmodel.cpp
models/notificationsmodel.h models/notificationsmodel.h
proxycontroller.cpp proxycontroller.cpp
@@ -59,7 +61,9 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/RoomPage.qml qml/RoomPage.qml
qml/ManualRoomDialog.qml qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml qml/ExplorerDelegate.qml
qml/ImageEditorPage.qml
qml/NeochatMaximizeComponent.qml qml/NeochatMaximizeComponent.qml
qml/TypingPane.qml
qml/QuickSwitcher.qml qml/QuickSwitcher.qml
qml/AttachmentPane.qml qml/AttachmentPane.qml
qml/QuickFormatBar.qml qml/QuickFormatBar.qml
@@ -104,11 +108,12 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
DEPENDENCIES DEPENDENCIES
QtCore QtCore
QtQuick QtQuick
org.kde.prison
org.kde.prison.scanner
IMPORTS IMPORTS
org.kde.neochat.libneochat org.kde.neochat.libneochat
org.kde.neochat.rooms org.kde.neochat.rooms
org.kde.neochat.roominfo org.kde.neochat.roominfo
org.kde.neochat.messagecontent
org.kde.neochat.timeline org.kde.neochat.timeline
org.kde.neochat.spaces org.kde.neochat.spaces
org.kde.neochat.settings org.kde.neochat.settings
@@ -177,7 +182,7 @@ else()
endif() endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models) target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin) target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PUBLIC target_link_libraries(neochat PUBLIC
LibNeoChat LibNeoChat
Timeline Timeline
@@ -202,7 +207,6 @@ target_link_libraries(neochat PUBLIC
QuotientQt6 QuotientQt6
Login Login
Rooms Rooms
MessageContent
Spaces Spaces
) )
@@ -357,10 +361,3 @@ install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE) if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins) install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif() endif()
if (APPLE)
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.kde.neochat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "NeoChat")
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${RELEASE_SERVICE_VERSION})
set_target_properties(neochat-app PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${RELEASE_SERVICE_VERSION})
endif ()

View File

@@ -407,9 +407,4 @@ void Controller::markImageShown(const QString &eventId)
m_shownImages.append(eventId); m_shownImages.append(eventId);
} }
void Controller::markImageHidden(const QString &eventId)
{
m_shownImages.removeAll(eventId);
}
#include "moc_controller.cpp" #include "moc_controller.cpp"

View File

@@ -99,7 +99,6 @@ public:
Q_INVOKABLE bool isImageShown(const QString &eventId); Q_INVOKABLE bool isImageShown(const QString &eventId);
Q_INVOKABLE void markImageShown(const QString &eventId); Q_INVOKABLE void markImageShown(const QString &eventId);
Q_INVOKABLE void markImageHidden(const QString &eventId);
private: private:
explicit Controller(QObject *parent = nullptr); explicit Controller(QObject *parent = nullptr);

223
src/app/logger.cpp Normal file
View File

@@ -0,0 +1,223 @@
// SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
// SPDX-FileCopyrightText: 2002 Holger Freyther <freyther@kde.org>
// SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
// SPDX-FileCopyrightText: 2023 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "logger.h"
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QLoggingCategory>
#include <QMutex>
#include <QStandardPaths>
using namespace Qt::StringLiterals;
static QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr;
static QtMessageHandler oldHandler = nullptr;
static bool e2eeDebugEnabled = false;
class FileDebugStream : public QIODevice
{
Q_OBJECT
public:
FileDebugStream()
: mType(QtCriticalMsg)
{
open(WriteOnly);
}
bool isSequential() const override
{
return true;
}
qint64 readData(char *, qint64) override
{
return 0;
}
qint64 readLineData(char *, qint64) override
{
return 0;
}
qint64 writeData(const char *data, qint64 len) override
{
if (!mFileName.isEmpty()) {
QFile outputFile(mFileName);
outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered);
outputFile.write(data, len);
outputFile.putChar('\n');
outputFile.close();
}
return len;
}
void setFileName(const QString &fileName)
{
mFileName = fileName;
}
void setType(QtMsgType type)
{
mType = type;
}
private:
QString mFileName;
QtMsgType mType;
};
class DebugPrivate
{
public:
DebugPrivate()
: origHandler(nullptr)
{
}
~DebugPrivate()
{
qInstallMessageHandler(origHandler);
file.close();
}
void log(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
QMutexLocker locker(&mutex);
QByteArray buf;
QTextStream str(&buf);
str << QDateTime::currentDateTime().toString(Qt::ISODate) << u" ["_s;
switch (type) {
case QtDebugMsg:
str << u"DEBUG"_s;
break;
case QtInfoMsg:
str << u"INFO "_s;
break;
case QtWarningMsg:
str << u"WARN "_s;
break;
case QtFatalMsg:
str << u"FATAL"_s;
break;
case QtCriticalMsg:
str << u"CRITICAL"_s;
break;
}
str << u"] "_s << context.category << u": "_s;
if (context.file && *context.file && context.line) {
str << context.file << u":"_s << context.line << u": "_s;
}
if (context.function && *context.function) {
str << context.function << u": "_s;
}
str << message << u"\n"_s;
str.flush();
file.write(buf.constData(), buf.size());
file.flush();
if (oldHandler && (!context.category || (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled))) {
oldHandler(type, context, message);
}
}
void setName(const QString &appName)
{
name = appName;
if (file.isOpen()) {
file.close();
}
const auto &filePath = u"%1%2%3"_s.arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), QDir::separator(), appName);
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator());
auto entryList = dir.entryList({appName + u".*"_s});
std::sort(entryList.begin(), entryList.end(), [](const auto &left, const auto &right) {
auto leftIndex = left.split(u"."_s).last().toInt();
auto rightIndex = right.split(u"."_s).last().toInt();
return leftIndex > rightIndex;
});
for (const auto &entry : entryList) {
bool ok = false;
const auto index = entry.split(u"."_s).last().toInt(&ok);
if (!ok) {
continue;
}
QFileInfo info(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + entry);
if (info.exists()) {
QFile file(info.absoluteFilePath());
if (index > 50) {
file.remove();
continue;
}
const auto &newName = u"%1.%2"_s.arg(filePath, QString::number(index + 1));
const auto success = file.copy(newName);
if (success) {
file.remove();
} else {
qFatal("Cannot rename log file '%s' to '%s': %s",
qUtf8Printable(file.fileName()),
qUtf8Printable(newName),
qUtf8Printable(file.errorString()));
}
}
}
QFileInfo finfo(filePath);
if (!finfo.absoluteDir().exists()) {
QDir().mkpath(finfo.absolutePath());
}
file.setFileName(filePath + u".0"_s);
file.open(QIODevice::WriteOnly | QIODevice::Unbuffered);
}
void setOrigHandler(QtMessageHandler origHandler_)
{
origHandler = origHandler_;
}
QMutex mutex;
QFile file;
QString name;
QtMessageHandler origHandler;
QByteArray loggingCategory;
};
Q_GLOBAL_STATIC(DebugPrivate, sInstance)
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
switch (type) {
case QtDebugMsg:
case QtInfoMsg:
case QtWarningMsg:
case QtCriticalMsg:
sInstance()->log(type, context, message);
break;
case QtFatalMsg:
sInstance()->log(QtInfoMsg, context, message);
}
}
void filter(QLoggingCategory *category)
{
if (qstrcmp(category->categoryName(), "quotient.e2ee") == 0) {
category->setEnabled(QtDebugMsg, true);
} else if (oldCategoryFilter) {
oldCategoryFilter(category);
}
}
void initLogging()
{
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtInfoMsg).isEnabled(QtDebugMsg);
oldCategoryFilter = QLoggingCategory::installFilter(filter);
oldHandler = qInstallMessageHandler(messageHandler);
sInstance->setOrigHandler(oldHandler);
sInstance->setName(u"neochat.log"_s);
}
#include "logger.moc"

9
src/app/logger.h Normal file
View File

@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
/**
* Initlalize logging to file and enables some additional categories, which will only be logged to the file
*/
void initLogging();

View File

@@ -49,6 +49,7 @@
#include "blurhashimageprovider.h" #include "blurhashimageprovider.h"
#include "colorschemer.h" #include "colorschemer.h"
#include "controller.h" #include "controller.h"
#include "logger.h"
#include "login.h" #include "login.h"
#include "registration.h" #include "registration.h"
#include "roommanager.h" #include "roommanager.h"
@@ -137,11 +138,6 @@ int main(int argc, char *argv[])
font.setHintingPreference(QFont::PreferNoHinting); font.setHintingPreference(QFont::PreferNoHinting);
app.setFont(font); app.setFont(font);
#endif #endif
#ifdef Q_OS_MACOS
QApplication::setStyle(u"breeze"_s);
#endif
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat")); KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
QGuiApplication::setOrganizationName("KDE"_L1); QGuiApplication::setOrganizationName("KDE"_L1);
@@ -181,6 +177,8 @@ int main(int argc, char *argv[])
KCrash::initialize(); KCrash::initialize();
#endif #endif
initLogging();
Connection::setEncryptionDefault(true); Connection::setEncryptionDefault(true);
Connection::setDirectChatEncryptionDefault(true); Connection::setDirectChatEncryptionDefault(true);
@@ -197,9 +195,6 @@ int main(int argc, char *argv[])
parser.addPositionalArgument(u"urls"_s, i18n("Supports matrix: url scheme")); parser.addPositionalArgument(u"urls"_s, i18n("Supports matrix: url scheme"));
parser.addOption(QCommandLineOption("ignore-ssl-errors"_L1, i18n("Ignore all SSL Errors, e.g., unsigned certificates."))); parser.addOption(QCommandLineOption("ignore-ssl-errors"_L1, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
QCommandLineOption replaceOption({QStringLiteral("replace")}, i18nc("command line description", "Replace an existing instance"));
parser.addOption(replaceOption);
QCommandLineOption testOption("test"_L1, i18n("Only used for autotests")); QCommandLineOption testOption("test"_L1, i18n("Only used for autotests"));
testOption.setFlags(QCommandLineOption::HiddenFromHelp); testOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(testOption); parser.addOption(testOption);
@@ -236,7 +231,7 @@ int main(int argc, char *argv[])
#endif #endif
#ifdef HAVE_KDBUSADDONS #ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique | (parser.isSet(replaceOption) ? KDBusService::Replace : KDBusService::StartupOption(0))); KDBusService service(KDBusService::Unique);
#endif #endif
const auto accountManager = std::make_unique<AccountManager>(parser.isSet("test"_L1)); const auto accountManager = std::make_unique<AccountManager>(parser.isSet("test"_L1));
@@ -244,6 +239,13 @@ int main(int argc, char *argv[])
LoginHelper::instance().setAccountManager(accountManager.get()); LoginHelper::instance().setAccountManager(accountManager.get());
Registration::instance().setAccountManager(accountManager.get()); Registration::instance().setAccountManager(accountManager.get());
Q_IMPORT_QML_PLUGIN(org_kde_neochat_settingsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_roomsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_loginPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
qml_register_types_org_kde_neochat(); qml_register_types_org_kde_neochat();
qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s); qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s);

View File

@@ -78,12 +78,6 @@
<label>Use a compact room list layout</label> <label>Use a compact room list layout</label>
<default>false</default> <default>false</default>
</entry> </entry>
<entry name="MarkReadCondition" type="Enum">
<label>The sort order for the rooms in the list.</label>
<choices name="::TimelineMarkReadCondition::Condition">
</choices>
<default>2</default>
</entry>
<entry name="ShowStateEvent" type="bool"> <entry name="ShowStateEvent" type="bool">
<label>Show state events in the timeline</label> <label>Show state events in the timeline</label>
<default>true</default> <default>true</default>
@@ -211,10 +205,6 @@
<label>Enable add phone numbers as 3PIDs</label> <label>Enable add phone numbers as 3PIDs</label>
<default>false</default> <default>false</default>
</entry> </entry>
<entry name="Calls" type="bool">
<label>Enable audio and video calling</label>
<default>false</default>
</entry>
</group> </group>
<group name="Security"> <group name="Security">
<entry name="RejectUnknownInvites" type="bool"> <entry name="RejectUnknownInvites" type="bool">

View File

@@ -36,18 +36,14 @@ KirigamiComponents.ConvergentContextMenu {
} }
} }
Kirigami.Action {
text: i18nc("@action:inmenu", "Switch Account")
icon.name: "system-switch-user"
shortcut: "Ctrl+U"
onTriggered: accountSwitchDialog.createObject(QQC2.Overlay.overlay, {
connection: root.connection
}).open();
}
QQC2.Action { QQC2.Action {
text: i18n("Edit This Account") text: i18n("Edit This Account")
icon.name: "document-edit" icon.name: "document-edit"
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection}); onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), {
connection: root.connection
}, {
title: i18n("Account editor")
})
} }
QQC2.Action { QQC2.Action {

View File

@@ -16,7 +16,7 @@ ColumnLayout {
id: root id: root
required property NeoChatRoom currentRoom required property NeoChatRoom currentRoom
readonly property var invitingMember: currentRoom.qmlSafeMember(currentRoom.invitingUserId) readonly property var invitingMember: currentRoom.member(currentRoom.invitingUserId)
readonly property string inviteTimestamp: root.currentRoom.inviteTimestamp.toLocaleString(Qt.locale(), Locale.ShortFormat) readonly property string inviteTimestamp: root.currentRoom.inviteTimestamp.toLocaleString(Qt.locale(), Locale.ShortFormat)
spacing: Kirigami.Units.smallSpacing spacing: Kirigami.Units.smallSpacing
@@ -33,7 +33,7 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
name: root.invitingMember.displayName name: root.invitingMember.displayName
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl source: root.invitingMember.avatarUrl
color: root.invitingMember.color color: root.invitingMember.color
} }

View File

@@ -41,7 +41,6 @@ Kirigami.ApplicationWindow {
showExisting: true showExisting: true
onConnectionChosen: root.load() onConnectionChosen: root.load()
} }
columnView.columnResizeMode: pageStack.wideMode ? Kirigami.ColumnView.DynamicColumns : Kirigami.ColumnView.SingleColumn
globalToolBar.canContainHandles: true globalToolBar.canContainHandles: true
globalToolBar { globalToolBar {
style: Kirigami.ApplicationHeaderStyle.ToolBar style: Kirigami.ApplicationHeaderStyle.ToolBar

View File

@@ -139,7 +139,7 @@ Components.AlbumMaximizeComponent {
id: saveAsDialog id: saveAsDialog
Dialogs.FileDialog { Dialogs.FileDialog {
fileMode: Dialogs.FileDialog.SaveFile fileMode: Dialogs.FileDialog.SaveFile
currentFolder: NeoChatConfig.lastSaveDirectory.length > 0 ? NeoChatConfig.lastSaveDirectory : Core.StandardPaths.writableLocation(Core.StandardPaths.DownloadLocation) currentFolder: root.saveFolder
onAccepted: { onAccepted: {
NeoChatConfig.lastSaveDirectory = currentFolder; NeoChatConfig.lastSaveDirectory = currentFolder;
NeoChatConfig.save(); NeoChatConfig.save();

View File

@@ -28,14 +28,6 @@ Kirigami.Page {
placeholderText: root.placeholder placeholderText: root.placeholder
anchors.fill: parent anchors.fill: parent
wrapMode: TextEdit.Wrap wrapMode: TextEdit.Wrap
focus: true
Keys.onReturnPressed: event => {
if (event.modifiers & Qt.ControlModifier) {
root.accepted(reason.text);
root.closeDialog();
}
}
background: Rectangle { background: Rectangle {
color: Kirigami.Theme.backgroundColor color: Kirigami.Theme.backgroundColor
@@ -58,7 +50,6 @@ Kirigami.Page {
} }
} }
QQC2.Button { QQC2.Button {
icon.name: "dialog-cancel-symbolic"
text: i18nc("@action", "Cancel") text: i18nc("@action", "Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: root.closeDialog() onClicked: root.closeDialog()

View File

@@ -11,14 +11,15 @@ import org.kde.kirigami as Kirigami
import org.kde.kitemmodels import org.kde.kitemmodels
import org.kde.neochat import org.kde.neochat
import org.kde.neochat.chatbar
Kirigami.Page { Kirigami.Page {
id: root id: root
/** /// Not readonly because of the separate window view.
* @brief The NeoChatRoom the delegate is being displayed in. property NeoChatRoom currentRoom: RoomManager.currentRoom
*/
readonly property NeoChatRoom currentRoom: RoomManager.currentRoom required property NeoChatConnection connection
/** /**
* @brief The TimelineModel to use. * @brief The TimelineModel to use.
@@ -58,6 +59,11 @@ Kirigami.Page {
*/ */
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
property bool loading: !root.currentRoom || (root.currentRoom.timelineSize === 0 && !root.currentRoom.allHistoryLoaded)
/// Disable cancel shortcut. Used by the separate window since it provides its own cancel implementation.
property bool disableCancelShortcut: false
title: root.currentRoom ? root.currentRoom.displayName : "" title: root.currentRoom ? root.currentRoom.displayName : ""
focus: true focus: true
padding: 0 padding: 0
@@ -80,9 +86,9 @@ Kirigami.Page {
} }
Connections { Connections {
target: root.currentRoom.connection target: root.connection
function onIsOnlineChanged() { function onIsOnlineChanged() {
if (!root.currentRoom.connection.isOnline) { if (!root.connection.isOnline) {
banner.text = i18n("NeoChat is offline. Please check your network connection."); banner.text = i18n("NeoChat is offline. Please check your network connection.");
banner.visible = true; banner.visible = true;
banner.type = Kirigami.MessageType.Error; banner.type = Kirigami.MessageType.Error;
@@ -103,15 +109,18 @@ Kirigami.Page {
Loader { Loader {
id: timelineViewLoader id: timelineViewLoader
anchors.fill: parent anchors.fill: parent
active: root.currentRoom && !root.currentRoom.isInvite && !root.currentRoom.isSpace active: root.currentRoom && !root.currentRoom.isInvite && !root.loading && !root.currentRoom.isSpace
// We need the loader to be active but invisible while the room is loading messages so signals in TimelineView work.
visible: !root.loading
sourceComponent: TimelineView { sourceComponent: TimelineView {
id: timelineView id: timelineView
currentRoom: root.currentRoom
page: root
timelineModel: root.timelineModel
messageFilterModel: root.messageFilterModel messageFilterModel: root.messageFilterModel
compactLayout: NeoChatConfig.compactLayout onFocusChatBar: {
fileDropEnabled: !Controller.isFlatpak if (chatBarLoader.item) {
markReadCondition: NeoChatConfig.markReadCondition chatBarLoader.item.forceActiveFocus();
}
}
} }
} }
@@ -143,6 +152,14 @@ Kirigami.Page {
} }
} }
Loader {
active: root.loading && !invitationLoader.active && RoomManager.currentRoom && !spaceLoader.active
anchors.centerIn: parent
sourceComponent: Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
}
}
background: Rectangle { background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false Kirigami.Theme.inherit: false
@@ -157,7 +174,12 @@ Kirigami.Page {
id: chatBar id: chatBar
width: parent.width width: parent.width
currentRoom: root.currentRoom currentRoom: root.currentRoom
connection: root.currentRoom.connection connection: root.connection
onMessageSent: {
if (!timelineViewLoader.item.atYEnd) {
timelineViewLoader.item.goToLastMessage();
}
}
} }
} }
@@ -174,8 +196,21 @@ Kirigami.Page {
} }
} }
Shortcut {
sequence: StandardKey.Cancel
onActivated: {
if (!timelineViewLoader.item.atYEnd || !root.currentRoom.partiallyReadStats.empty()) {
timelineViewLoader.item.goToLastMessage();
root.currentRoom.markAllMessagesAsRead();
} else {
applicationWindow().pageStack.get(0).forceActiveFocus();
}
}
enabled: !root.disableCancelShortcut
}
Connections { Connections {
target: root.currentRoom.connection target: root.connection
function onJoinedRoom(room, invited) { function onJoinedRoom(room, invited) {
if (root.currentRoom.id === invited.id) { if (root.currentRoom.id === invited.id) {
RoomManager.resolveResource(room.id); RoomManager.resolveResource(room.id);
@@ -261,7 +296,7 @@ Kirigami.Page {
id: messageDelegateContextMenu id: messageDelegateContextMenu
MessageDelegateContextMenu { MessageDelegateContextMenu {
room: root.currentRoom room: root.currentRoom
connection: root.currentRoom.connection connection: root.connection
} }
} }
@@ -269,7 +304,7 @@ Kirigami.Page {
id: fileDelegateContextMenu id: fileDelegateContextMenu
FileDelegateContextMenu { FileDelegateContextMenu {
room: root.currentRoom room: root.currentRoom
connection: root.currentRoom.connection connection: root.connection
} }
} }

View File

@@ -80,6 +80,7 @@ Kirigami.Dialog {
text: root.user.id text: root.user.id
elide: Qt.ElideRight elide: Qt.ElideRight
elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
onElideWidthChanged: console.warn(root.availableWidth, avatar.width, qrButton.width, elideWidth, elidedText)
} }
} }

View File

@@ -59,9 +59,9 @@ RoomManager::RoomManager(QObject *parent)
m_directChatsConfig = m_config->group(u"DirectChatsActive"_s); m_directChatsConfig = m_config->group(u"DirectChatsActive"_s);
connect(this, &RoomManager::currentRoomChanged, this, [this]() { connect(this, &RoomManager::currentRoomChanged, this, [this]() {
m_userListModel->setRoom(m_currentRoom);
m_timelineModel->setRoom(m_currentRoom); m_timelineModel->setRoom(m_currentRoom);
m_sortFilterRoomTreeModel->setCurrentRoom(m_currentRoom); m_sortFilterRoomTreeModel->setCurrentRoom(m_currentRoom);
m_userListModel->setRoom(m_currentRoom);
}); });
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this](NeoChatConnection *connection) { connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this](NeoChatConnection *connection) {
@@ -96,7 +96,6 @@ RoomManager::RoomManager(QObject *parent)
m_messageFilterModel->invalidate(); m_messageFilterModel->invalidate();
} }
}); });
connect(m_timelineModel->timelineMessageModel(), &MessageModel::modelResetComplete, this, &RoomManager::activateUserModel);
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents()); MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());
connect(NeoChatConfig::self(), &NeoChatConfig::ShowAllEventsChanged, this, [this] { connect(NeoChatConfig::self(), &NeoChatConfig::ShowAllEventsChanged, this, [this] {
MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents()); MessageFilterModel::setShowAllEvents(NeoChatConfig::self()->showAllEvents());

View File

@@ -15,5 +15,4 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE
EmojiPicker.qml EmojiPicker.qml
EmojiDialog.qml EmojiDialog.qml
EmojiTonesPicker.qml EmojiTonesPicker.qml
ImageEditorPage.qml
) )

View File

@@ -160,6 +160,11 @@ QQC2.Control {
} }
] ]
/**
* @brief A message has been sent from the chat bar.
*/
signal messageSent
spacing: 0 spacing: 0
Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.colorSet: Kirigami.Theme.View
@@ -431,6 +436,7 @@ QQC2.Control {
repeatTimer.stop(); repeatTimer.stop();
root.currentRoom.markAllMessagesAsRead(); root.currentRoom.markAllMessagesAsRead();
textField.clear(); textField.clear();
messageSent();
} }
function formatText(format, selectionStart, selectionEnd) { function formatText(format, selectionStart, selectionEnd) {

View File

@@ -207,7 +207,7 @@ ColumnLayout {
padding: Kirigami.Units.largeSpacing padding: Kirigami.Units.largeSpacing
contentItem: Image { contentItem: Image {
source: model.url source: model.avatarUrl
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
sourceSize.width: width sourceSize.width: width
sourceSize.height: height sourceSize.height: height

View File

@@ -36,13 +36,4 @@ FormCard.FormCard {
NeoChatConfig.save(); NeoChatConfig.save();
} }
} }
FormCard.FormCheckDelegate {
text: i18nc("@option:check Enable the matrix feature for audio and video calling", "Calls")
checked: NeoChatConfig.calls
onToggled: {
NeoChatConfig.calls = checked;
NeoChatConfig.save();
}
}
} }

View File

@@ -28,7 +28,6 @@ target_sources(LibNeoChat PRIVATE
enums/pushrule.h enums/pushrule.h
enums/roomsortparameter.cpp enums/roomsortparameter.cpp
enums/roomsortorder.h enums/roomsortorder.h
enums/timelinemarkreadcondition.h
events/imagepackevent.cpp events/imagepackevent.cpp
events/pollevent.cpp events/pollevent.cpp
jobs/neochatgetcommonroomsjob.cpp jobs/neochatgetcommonroomsjob.cpp
@@ -49,6 +48,11 @@ target_sources(LibNeoChat PRIVATE
ecm_add_qml_module(LibNeoChat GENERATE_PLUGIN_SOURCE ecm_add_qml_module(LibNeoChat GENERATE_PLUGIN_SOURCE
URI org.kde.neochat.libneochat URI org.kde.neochat.libneochat
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/libneochat OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/libneochat
DEPENDENCIES
QtCore
QtQuick
org.kde.prison
org.kde.prison.scanner
QML_FILES QML_FILES
qml/GroupChatDrawerHeader.qml qml/GroupChatDrawerHeader.qml
qml/LocationMapItem.qml qml/LocationMapItem.qml

View File

@@ -1,32 +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 <QQmlEngine>
/**
* @class TimelineMarkReadCondition
*
* This class is designed to define the TimelineMarkReadCondition enumeration.
*/
class TimelineMarkReadCondition : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief The condition for marking messages as read.
*/
enum Condition {
Never = 0, /**< Messages should never be marked automatically. */
Entry, /**< Messages should be marked automatically on entry to the room. */
EntryVisible, /**< Messages should be marked automatically on entry to the room if all messages are visible. */
Exit, /**< Messages should be marked automatically on exiting the room. */
ExitVisible, /**< Messages should be marked automatically on exiting the room if all messages are visible. */
};
Q_ENUM(Condition);
};

View File

@@ -435,13 +435,6 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"_L1].toString()); return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"_L1].toString());
}, },
[prettyPrint](const StateEvent &e) { [prettyPrint](const StateEvent &e) {
if (e.matrixType() == "org.matrix.msc3401.call.member"_L1) {
if (e.contentJson().isEmpty()) {
return i18nc("[User] left a [voice/video] call", "left a call");
} else {
return i18nc("[User] joined a [voice/video] call", "joined a call");
}
}
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType()) return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey()); : i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
}, },
@@ -641,14 +634,7 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
} }
return i18n("%1 configured a widget", senderString); return i18n("%1 configured a widget", senderString);
}, },
[senderString](const StateEvent &e) { [senderString](const StateEvent &) {
if (e.matrixType() == "org.matrix.msc3401.call.member"_L1) {
if (e.contentJson().isEmpty()) {
return i18nc("[User] left a [voice/video] call", "%1 left a call", senderString);
} else {
return i18nc("[User] joined a [voice/video] call", "%1 joined a call", senderString);
}
}
return i18n("%1 updated the state", senderString); return i18n("%1 updated the state", senderString);
}, },
[senderString](const PollStartEvent &) { [senderString](const PollStartEvent &) {

View File

@@ -10,7 +10,7 @@ struct MessageComponent {
QString content; QString content;
QVariantMap attributes; QVariantMap attributes;
bool operator==(const MessageComponent &right) const int operator==(const MessageComponent &right) const
{ {
return type == right.type && content == right.content && attributes == right.attributes; return type == right.type && content == right.content && attributes == right.attributes;
} }

View File

@@ -1214,38 +1214,34 @@ QByteArray NeoChatRoom::getEventJsonSource(const QString &eventId)
void NeoChatRoom::openEventMediaExternally(const QString &eventId) void NeoChatRoom::openEventMediaExternally(const QString &eventId)
{ {
const auto evtIt = findInTimeline(eventId); const auto evtIt = findInTimeline(eventId);
if (evtIt == messageEvents().rend()) { if (evtIt != messageEvents().rend() && is<RoomMessageEvent>(**evtIt)) {
return; const auto event = evtIt->viewAs<RoomMessageEvent>();
} if (event->has<EventContent::FileContent>()) {
const auto transferInfo = cachedFileTransferInfo(event);
// TODO: Also allow stickers here, once that's fixed in libQuotient if (transferInfo.completed()) {
if (!is<RoomMessageEvent>(**evtIt) || !evtIt->viewAs<RoomMessageEvent>()->has<EventContent::FileContentBase>()) {
return;
}
const auto transferInfo = cachedFileTransferInfo(evtIt->viewAs<RoomEvent>());
if (transferInfo.completed()) {
UrlHelper helper;
helper.openUrl(transferInfo.localPath);
return;
}
downloadFile(eventId,
QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/'
+ evtIt->event()->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId)));
connect(
this,
&Room::fileTransferCompleted,
this,
[this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
Q_UNUSED(localFile);
Q_UNUSED(fileMetadata);
if (id == eventId) {
auto transferInfo = fileTransferInfo(eventId);
UrlHelper helper; UrlHelper helper;
helper.openUrl(transferInfo.localPath); helper.openUrl(transferInfo.localPath);
} else {
downloadFile(eventId,
QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/'
+ event->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId)));
connect(
this,
&Room::fileTransferCompleted,
this,
[this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
Q_UNUSED(localFile);
Q_UNUSED(fileMetadata);
if (id == eventId) {
auto transferInfo = fileTransferInfo(eventId);
UrlHelper helper;
helper.openUrl(transferInfo.localPath);
}
},
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
} }
}, }
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); }
} }
void NeoChatRoom::copyEventMedia(const QString &eventId) void NeoChatRoom::copyEventMedia(const QString &eventId)

View File

@@ -557,7 +557,7 @@ public:
* responsibility of the caller to ensure that they only ask for objects * responsibility of the caller to ensure that they only ask for objects
* for real senders. * for real senders.
*/ */
Q_INVOKABLE NeochatRoomMember *qmlSafeMember(const QString &memberId); NeochatRoomMember *qmlSafeMember(const QString &memberId);
/** /**
* @brief Pin a message in the room. * @brief Pin a message in the room.

View File

@@ -1,107 +0,0 @@
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(MessageContent STATIC)
ecm_add_qml_module(MessageContent GENERATE_PLUGIN_SOURCE
URI org.kde.neochat.messagecontent
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/messagecontent
QML_FILES
BaseMessageComponentChooser.qml
MessageComponentChooser.qml
ReplyMessageComponentChooser.qml
AuthorComponent.qml
AudioComponent.qml
ChatBarComponent.qml
CodeComponent.qml
EncryptedComponent.qml
FetchButtonComponent.qml
FileComponent.qml
ImageComponent.qml
ItineraryComponent.qml
ItineraryReservationComponent.qml
JourneySectionStopDelegateLineSegment.qml
TransportIcon.qml
FoodReservationComponent.qml
TrainReservationComponent.qml
FlightReservationComponent.qml
HotelReservationComponent.qml
LinkPreviewComponent.qml
LinkPreviewLoadComponent.qml
LiveLocationComponent.qml
LoadComponent.qml
LocationComponent.qml
MimeComponent.qml
PdfPreviewComponent.qml
PollComponent.qml
QuoteComponent.qml
ReactionComponent.qml
ReplyAuthorComponent.qml
ReplyButtonComponent.qml
ReplyComponent.qml
StateComponent.qml
TextComponent.qml
ThreadBodyComponent.qml
VideoComponent.qml
SOURCES
contentprovider.cpp
mediasizehelper.cpp
pollhandler.cpp
models/itinerarymodel.cpp
models/linemodel.cpp
models/messagecontentmodel.cpp
models/pollanswermodel.cpp
models/reactionmodel.cpp
models/threadmodel.cpp
RESOURCES
images/bike.svg
images/bus.svg
images/cablecar.svg
images/car.svg
images/coach.svg
images/couchettecar.svg
images/elevator.svg
images/escalator.svg
images/ferry.svg
images/flight.svg
images/foodestablishment.svg
images/funicular.svg
images/longdistancetrain.svg
images/rapidtransit.svg
images/seat.svg
images/shuttle.svg
images/sleepingcar.svg
images/stairs.svg
images/subway.svg
images/taxi.svg
images/train.svg
images/tramway.svg
images/transfer.svg
images/wait.svg
images/walk.svg
DEPENDENCIES
QtQuick
)
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
ecm_qt_declare_logging_category(MessageContent
HEADER "messagemodel_logging.h"
IDENTIFIER "Message"
CATEGORY_NAME "org.kde.neochat.messagemodel"
DESCRIPTION "Neochat: messagemodel"
DEFAULT_SEVERITY Info
EXPORT NEOCHAT
)
target_include_directories(MessageContent PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(MessageContent PRIVATE
Qt::Core
Qt::Quick
Qt::QuickControls2
KF6::Kirigami
LibNeoChat
)
if(NOT ANDROID)
target_link_libraries(MessageContent PUBLIC KF6::SyntaxHighlighting)
endif()

View File

@@ -14,10 +14,4 @@ ecm_add_qml_module(RoomInfo GENERATE_PLUGIN_SOURCE
LocationsPage.qml LocationsPage.qml
RoomPinnedMessagesPage.qml RoomPinnedMessagesPage.qml
RoomSearchPage.qml RoomSearchPage.qml
SOURCES
locationhelper.cpp
)
target_link_libraries(RoomInfo PRIVATE
Qt::Core
) )

View File

@@ -17,22 +17,13 @@ import org.kde.neochat
Kirigami.Page { Kirigami.Page {
id: root id: root
Kirigami.ColumnView.interactiveResizeEnabled: true /**
Kirigami.ColumnView.minimumWidth: _private.collapsedSize + spaceDrawer.width + 1 * @brief The current width of the room list.
Kirigami.ColumnView.maximumWidth: _private.defaultWidth + spaceDrawer.width + 1 *
Kirigami.ColumnView.onInteractiveResizingChanged: { * @note Other objects can access the value but the private function makes sure
if (!Kirigami.ColumnView.interactiveResizing && collapsed) { * that only the internal members can modify it.
Kirigami.ColumnView.preferredWidth = root.Kirigami.ColumnView.minimumWidth; */
} readonly property int currentWidth: _private.currentWidth + spaceDrawer.width + 1
}
Kirigami.ColumnView.preferredWidth: _private.currentWidth + spaceDrawer.width + 1
Kirigami.ColumnView.onPreferredWidthChanged: {
if (width > _private.collapseWidth) {
NeoChatConfig.collapsed = false;
} else if (Kirigami.ColumnView.interactiveResizing) {
NeoChatConfig.collapsed = true;
}
}
required property NeoChatConnection connection required property NeoChatConnection connection
@@ -40,6 +31,10 @@ Kirigami.Page {
signal search signal search
onCurrentWidthChanged: pageStack.defaultColumnWidth = root.currentWidth
Component.onCompleted: pageStack.defaultColumnWidth = root.currentWidth
onCollapsedChanged: { onCollapsedChanged: {
if (collapsed) { if (collapsed) {
RoomManager.sortFilterRoomTreeModel.filterText = ""; RoomManager.sortFilterRoomTreeModel.filterText = "";
@@ -249,6 +244,49 @@ Kirigami.Page {
sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
} }
MouseArea {
anchors.top: parent.top
anchors.bottom: parent.bottom
parent: applicationWindow().overlay.parent
x: root.currentWidth - width / 2
width: Kirigami.Units.smallSpacing * 2
z: root.z + 1
enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35
visible: enabled
cursorShape: Qt.SplitHCursor
property int _lastX
onPressed: mouse => {
_lastX = mouse.x;
}
onPositionChanged: mouse => {
if (_lastX == -1) {
return;
}
if (mouse.x > _lastX) {
// we moved to the right
if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) {
// Here we get back directly to a more wide mode.
_private.currentWidth = _private.defaultWidth;
NeoChatConfig.collapsed = false;
} else if (_private.currentWidth >= _private.collapseWidth) {
// Increase page width
_private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX));
}
} else if (mouse.x < _lastX) {
const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
if (tmpWidth < _private.collapseWidth) {
_private.currentWidth = Qt.binding(() => _private.collapsedSize);
NeoChatConfig.collapsed = true;
} else {
_private.currentWidth = tmpWidth;
}
}
}
}
Component { Component {
id: userInfo id: userInfo
UserInfo { UserInfo {

View File

@@ -38,14 +38,26 @@ QQC2.ItemDelegate {
Keys.onSpacePressed: root.treeView.toggleExpanded(row) Keys.onSpacePressed: root.treeView.toggleExpanded(row)
contentItem: RowLayout { contentItem: RowLayout {
spacing: 0 spacing: Kirigami.Units.largeSpacing
Kirigami.ListSectionHeader {
Kirigami.Heading {
Layout.alignment: Qt.AlignVCenter
visible: !root.collapsed
opacity: 0.7
level: 5
type: Kirigami.Heading.Primary
text: root.collapsed ? "" : model.displayName
elide: Text.ElideRight
// we override the Primary type's font weight (DemiBold) for Bold for contrast with small text
font.weight: Font.Bold
}
Kirigami.Separator {
Layout.fillWidth: true Layout.fillWidth: true
visible: !root.collapsed visible: !root.collapsed
horizontalPadding: 0 Layout.alignment: Qt.AlignVCenter
topPadding: 0
bottomPadding: 0
text: root.collapsed ? "" : root.displayName
} }
QQC2.ToolButton { QQC2.ToolButton {
id: collapseButton id: collapseButton

View File

@@ -95,19 +95,31 @@ RowLayout {
window: QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow window: QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow
} }
} }
QQC2.ToolButton {
display: QQC2.Button.IconOnly
action: Kirigami.Action {
text: i18n("Open Settings")
icon.name: "settings-configure-symbolic"
onTriggered: {
NeoChatSettingsView.open();
}
}
QQC2.ToolTip.text: text Kirigami.ActionToolBar {
QQC2.ToolTip.visible: hovered alignment: Qt.AlignRight
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay display: QQC2.Button.IconOnly
Layout.fillWidth: true
Layout.preferredWidth: maximumContentWidth
actions: [
Kirigami.Action {
text: i18n("Switch User")
icon.name: "system-switch-user"
shortcut: "Ctrl+U"
onTriggered: accountSwitchDialog.createObject(QQC2.Overlay.overlay, {
connection: root.connection
}).open();
},
Kirigami.Action {
text: i18n("Open Settings")
icon.name: "settings-configure-symbolic"
onTriggered: {
NeoChatSettingsView.open();
}
}
]
} }
Component { Component {

View File

@@ -138,7 +138,7 @@ FormCard.FormCardPage {
icon.name: "document-save-symbolic" icon.name: "document-save-symbolic"
onClicked: { onClicked: {
if (!root.connection.setAvatar(avatar.source)) { if (!root.connection.setAvatar(avatar.source)) {
(root.Window.window as Kirigami.ApplicationWindow).showPassiveNotification("The Avatar could not be set"); showPassiveNotification("The Avatar could not be set");
} }
if (root.connection.localUser.displayName !== name.text) { if (root.connection.localUser.displayName !== name.text) {
root.connection.localUser.rename(name.text); root.connection.localUser.rename(name.text);

View File

@@ -134,45 +134,9 @@ FormCard.FormCardPage {
} }
} }
FormCard.FormHeader { FormCard.FormHeader {
title: i18nc("@title", "Timeline") title: i18n("Timeline Events")
} }
FormCard.FormCard { FormCard.FormCard {
FormCard.FormComboBoxDelegate {
id: markAsReadCombo
text: i18n("Mark messages as read when:")
textRole: "name"
valueRole: "value"
model: [
{
name: i18n("Never"),
value: 0
},
{
name: i18nc("@item:inlistbox As in mark messages in the room as read when entering the room", "Entering the room"),
value: 1
},
{
name: i18nc("@item:inlistbox As in mark messages in the room as read when entering the room and all messages are visible on screen", "Entering the room and all unread messages are visible"),
value: 2
},
{
name: i18nc("@item:inlistbox As in mark messages in the room as read when exiting the room", "Exiting the room"),
value: 3
},
{
name: i18nc("@item:inlistbox As in mark messages in the room as read when exiting the room and all messages are visible on screen", "Exiting the room and all unread messages are visible"),
value: 4
}
]
Component.onCompleted: currentIndex = NeoChatConfig.markReadCondition
onCurrentValueChanged: NeoChatConfig.markReadCondition = currentValue
}
FormCard.FormDelegateSeparator {
above: markAsReadCombo
below: showDeletedMessages
}
FormCard.FormCheckDelegate { FormCard.FormCheckDelegate {
id: showDeletedMessages id: showDeletedMessages
text: i18n("Show deleted messages") text: i18n("Show deleted messages")
@@ -345,16 +309,8 @@ FormCard.FormCardPage {
FormCard.FormCard { FormCard.FormCard {
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
icon.name: "kt-restore-defaults-symbolic" icon.name: "kt-restore-defaults-symbolic"
text: i18nc("@action:button", "Reset all configuration values to their default") text: i18nc("@action:button", "Reset All Configuration Values to Their Default")
onClicked: resetDialog.open() onClicked: Controller.revertToDefaultConfig()
} }
} }
Kirigami.PromptDialog {
id: resetDialog
title: i18nc("@title:dialog", "Reset Configuration")
subtitle: i18nc("@info", "Do you really want to reset all options to their default values?")
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
onAccepted: Controller.revertToDefaultConfig()
anchors.centerIn: QQC2.Overlay.overlay
}
} }

View File

@@ -59,7 +59,6 @@ KirigamiSettings.ConfigurationView {
visible: root.connection !== null visible: root.connection !== null
}, },
KirigamiSettings.ConfigurationModule { KirigamiSettings.ConfigurationModule {
id: accountsModule
moduleId: "accounts" moduleId: "accounts"
text: i18n("Accounts") text: i18n("Accounts")
icon.name: "preferences-system-users-symbolic" icon.name: "preferences-system-users-symbolic"
@@ -112,14 +111,4 @@ KirigamiSettings.ConfigurationView {
category: i18nc("@title:group", "About") category: i18nc("@title:group", "About")
} }
] ]
function openWithInitialProperties(defaultModule = '', initialProperties): void {
let module = modules.find(module => module.moduleId == defaultModule) ?? null;
if (module) {
module.initialProperties = () => {
return initialProperties;
}
}
root.open(defaultModule);
}
} }

View File

@@ -18,6 +18,15 @@ using namespace Quotient;
DevicesModel::DevicesModel(QObject *parent) DevicesModel::DevicesModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {
connect(m_connection, &Connection::sessionVerified, this, [this](const QString &, const QString &deviceId) {
const auto it = std::find_if(m_devices.begin(), m_devices.end(), [deviceId](const Quotient::Device &device) {
return device.deviceId == deviceId;
});
if (it != m_devices.end()) {
const auto index = this->index(it - m_devices.begin());
Q_EMIT dataChanged(index, index, {Type});
}
});
} }
void DevicesModel::fetchDevices() void DevicesModel::fetchDevices()
@@ -153,15 +162,6 @@ void DevicesModel::setConnection(NeoChatConnection *connection)
disconnect(m_connection, nullptr, this, nullptr); disconnect(m_connection, nullptr, this, nullptr);
} }
m_connection = connection; m_connection = connection;
connect(m_connection, &Connection::sessionVerified, this, [this](const QString &, const QString &deviceId) {
const auto it = std::find_if(m_devices.begin(), m_devices.end(), [deviceId](const Quotient::Device &device) {
return device.deviceId == deviceId;
});
if (it != m_devices.end()) {
const auto index = this->index(it - m_devices.begin());
Q_EMIT dataChanged(index, index, {Type});
}
});
Q_EMIT connectionChanged(); Q_EMIT connectionChanged();
fetchDevices(); fetchDevices();

View File

@@ -9,7 +9,6 @@ import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.labs.components as Components import org.kde.kirigamiaddons.labs.components as Components
import org.kde.neochat
import org.kde.neochat.libneochat import org.kde.neochat.libneochat
Item { Item {

View File

@@ -13,42 +13,127 @@ ecm_add_qml_module(Timeline GENERATE_PLUGIN_SOURCE
LoadingDelegate.qml LoadingDelegate.qml
PredecessorDelegate.qml PredecessorDelegate.qml
ReadMarkerDelegate.qml ReadMarkerDelegate.qml
SpacerDelegate.qml
StateDelegate.qml StateDelegate.qml
SuccessorDelegate.qml SuccessorDelegate.qml
TimelineEndDelegate.qml TimelineEndDelegate.qml
Bubble.qml Bubble.qml
AvatarFlow.qml AvatarFlow.qml
SectionDelegate.qml
QuickActions.qml QuickActions.qml
TypingPane.qml BaseMessageComponentChooser.qml
MessageComponentChooser.qml
ReplyMessageComponentChooser.qml
AuthorComponent.qml
AudioComponent.qml
ChatBarComponent.qml
CodeComponent.qml
EncryptedComponent.qml
FetchButtonComponent.qml
FileComponent.qml
ImageComponent.qml
ItineraryComponent.qml
ItineraryReservationComponent.qml
JourneySectionStopDelegateLineSegment.qml
TransportIcon.qml
FoodReservationComponent.qml
TrainReservationComponent.qml
FlightReservationComponent.qml
HotelReservationComponent.qml
LinkPreviewComponent.qml
LinkPreviewLoadComponent.qml
LiveLocationComponent.qml
LoadComponent.qml
LocationComponent.qml
MimeComponent.qml
PdfPreviewComponent.qml
PollComponent.qml
QuoteComponent.qml
ReactionComponent.qml
ReplyAuthorComponent.qml
ReplyButtonComponent.qml
ReplyComponent.qml
StateComponent.qml
TextComponent.qml
ThreadBodyComponent.qml
VideoComponent.qml
DelegateContextMenu.qml DelegateContextMenu.qml
FileDelegateContextMenu.qml FileDelegateContextMenu.qml
MessageDelegateContextMenu.qml MessageDelegateContextMenu.qml
SOURCES SOURCES
contentprovider.cpp
locationhelper.cpp
mediasizehelper.cpp
messageattached.cpp messageattached.cpp
messagedelegate.cpp messagedelegate.cpp
pollhandler.cpp
timelinedelegate.cpp timelinedelegate.cpp
enums/delegatetype.h enums/delegatetype.h
models/itinerarymodel.cpp
models/linemodel.cpp
models/mediamessagefiltermodel.cpp models/mediamessagefiltermodel.cpp
models/messagecontentmodel.cpp
models/messagecontentfiltermodel.cpp models/messagecontentfiltermodel.cpp
models/messagefiltermodel.cpp models/messagefiltermodel.cpp
models/messagemodel.cpp models/messagemodel.cpp
models/pinnedmessagemodel.cpp models/pinnedmessagemodel.cpp
models/pollanswermodel.cpp
models/reactionmodel.cpp
models/readmarkermodel.cpp models/readmarkermodel.cpp
models/searchmodel.cpp models/searchmodel.cpp
models/timelinemessagemodel.cpp models/timelinemessagemodel.cpp
models/timelinemodel.cpp models/timelinemodel.cpp
models/threadmodel.cpp
models/webshortcutmodel.cpp models/webshortcutmodel.cpp
RESOURCES
images/bike.svg
images/bus.svg
images/cablecar.svg
images/car.svg
images/coach.svg
images/couchettecar.svg
images/elevator.svg
images/escalator.svg
images/ferry.svg
images/flight.svg
images/foodestablishment.svg
images/funicular.svg
images/longdistancetrain.svg
images/rapidtransit.svg
images/seat.svg
images/shuttle.svg
images/sleepingcar.svg
images/stairs.svg
images/subway.svg
images/taxi.svg
images/train.svg
images/tramway.svg
images/transfer.svg
images/wait.svg
images/walk.svg
DEPENDENCIES DEPENDENCIES
QtQuick QtQuick
) )
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
ecm_qt_declare_logging_category(Timeline
HEADER "messagemodel_logging.h"
IDENTIFIER "Message"
CATEGORY_NAME "org.kde.neochat.messagemodel"
DESCRIPTION "Neochat: messagemodel"
DEFAULT_SEVERITY Info
EXPORT NEOCHAT
)
target_include_directories(Timeline PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/enums ${CMAKE_CURRENT_SOURCE_DIR}/models) target_include_directories(Timeline PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/enums ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(Timeline PRIVATE target_link_libraries(Timeline PRIVATE
LibNeoChat
Qt::Core Qt::Core
Qt::Quick Qt::Quick
Qt::QuickControls2 Qt::QuickControls2
KF6::Kirigami KF6::Kirigami
LibNeoChat
MessageContent
) )
if(NOT ANDROID)
target_link_libraries(Timeline PUBLIC KF6::SyntaxHighlighting)
endif()

View File

@@ -62,11 +62,6 @@ DelegateChooser {
} }
} }
DelegateChoice {
roleValue: DelegateType.Spacer
delegate: SpacerDelegate {}
}
DelegateChoice { DelegateChoice {
roleValue: DelegateType.Other roleValue: DelegateType.Other
delegate: NeoChatConfig.showAllEvents ? hiddenDelegate : emptyDelegate delegate: NeoChatConfig.showAllEvents ? hiddenDelegate : emptyDelegate

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