Compare commits

..

16 Commits

Author SHA1 Message Date
l10n daemon script
96a477f91c GIT_SILENT Sync po/docbooks with svn 2024-01-10 02:13:57 +00:00
Tobias Fella
5a6b0f756d Remove broken network error checks
Fixes #529
2024-01-09 19:59:23 +01:00
l10n daemon script
dbbf975f7a GIT_SILENT Sync po/docbooks with svn 2024-01-09 02:10:55 +00:00
l10n daemon script
e06c2d2f93 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-01-09 02:06:01 +00:00
Albert Astals Cid
5dea17b616 GIT_SILENT Upgrade release service version to 24.01.90. 2024-01-09 00:47:19 +01:00
Tobias Fella
63b6d7ebe0 Remove unused includes 2024-01-08 20:41:59 +01:00
l10n daemon script
652106e6a1 GIT_SILENT Sync po/docbooks with svn 2024-01-08 02:16:01 +00:00
Joshua Goins
3e158b3e60 Stop log spam because subtitleText was called without a valid event
It's up to the call site to check if the event is valid before calling
this function, and it prevents tons of log spam because we didn't check
yet.
2024-01-07 21:43:26 +00:00
Tobias Fella
b9ec33dd94 Remove activeConnection from Config
It's not used anymore
2024-01-07 21:25:55 +01:00
Tobias Fella
69bd1202ba Remove another comment 2024-01-07 21:20:04 +01:00
Tobias Fella
f75c09e130 Remove comment 2024-01-07 21:19:25 +01:00
Tobias Fella
683d216f44 Mark ReactionModel as uncreatable 2024-01-07 19:47:19 +01:00
Tobias Fella
c10bcf1764 Move userConsentRequired to NeoChatConnection 2024-01-07 19:44:37 +01:00
Tobias Fella
9e2bf0da26 Port away from Controller::saveWindowGeometry 2024-01-07 19:39:18 +01:00
James Graham
51f7de117d Refactor LinkPreviewer
Refactor `LinkPreviewer` to take an event and put the functions for getting the link in the class itself. This means the functions in `EventHandler` are no longer required.

This mr also sets up `LinkPreviewer` so that it is automatically updated when an event is edited. This includes changing the link if edited, and it can handle a message having a previous link removed or a one added when one didn't exist before.

Also adds test suite.
2024-01-07 18:07:13 +00:00
Tobias Fella
f361f4e2d8 Don't always html-escape user-specified input when serializing a state event body
We don't want this for the room list subtitle
2024-01-07 16:12:45 +00:00
90 changed files with 8788 additions and 8735 deletions

View File

@@ -9,7 +9,7 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "01")
set(RELEASE_SERVICE_VERSION_MICRO "85")
set(RELEASE_SERVICE_VERSION_MICRO "90")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})

View File

@@ -76,3 +76,9 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test
TEST_NAME reactionmodeltest
)
ecm_add_test(
linkpreviewertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME linkpreviewertest
)

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "https://matrix.to/#/@alice:example.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "mxc://example.org/SEsfnsuifSDFSSEF",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "testhttps://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,24 @@
{
"timeline": {
"events": [
{
"content": {
"body": "https://kde.org",
"format": "org.matrix.custom.html",
"formatted_body": "https://kde.org",
"msgtype": "m.text"
},
"origin_server_ts": 1704648567967,
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 112
},
"event_id": "$validlink:example.org",
"room_id": "!test:example.org"
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,35 @@
{
"timeline": {
"events": [
{
"content": {
"body": "* ",
"format": "org.matrix.custom.html",
"formatted_body": "no link",
"m.new_content": {
"body": "",
"format": "org.matrix.custom.html",
"formatted_body": "no link",
"msgtype": "m.text"
},
"m.relates_to": {
"event_id": "$validlink:example.org",
"rel_type": "m.replace"
},
"msgtype": "m.text",
"type": "m.room.message"
},
"origin_server_ts": 1704648614969,
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 65
},
"event_id": "$nolink:example.org",
"room_id": "!test:example.org"
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "www.example.org https://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "https://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "www.example.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,16 @@
{
"content": {
"body": "[Rich Link](https://kde.org)",
"format": "org.matrix.custom.html",
"formatted_body": "<a href=\"https://kde.org\">Rich Link</a>",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -65,8 +65,6 @@ private Q_SLOTS:
void nullSubtitle();
void mediaInfo();
void nullMediaInfo();
void linkPreviewer();
void nullLinkPreviewer();
void hasReply();
void nullHasReply();
void replyId();
@@ -399,28 +397,6 @@ void EventHandlerTest::nullMediaInfo()
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
}
void EventHandlerTest::linkPreviewer()
{
auto event = room->messageEvents().at(2).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.getLinkPreviewer()->url(), QUrl("https://kde.org"_ls));
event = room->messageEvents().at(0).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::nullLinkPreviewer()
{
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getLinkPreviewer(), nullptr);
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::hasReply()
{
auto event = room->messageEvents().at(5).get();

View File

@@ -0,0 +1,104 @@
// 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 "linkpreviewer.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "utils.h"
#include "testutils.h"
using namespace Quotient;
class LinkPreviewerTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
void editedLink();
};
void LinkPreviewerTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:example.org"));
room = new TestUtils::TestRoom(connection, QStringLiteral("!test:example.org"));
}
void LinkPreviewerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("eventSource");
QTest::addColumn<QUrl>("testOutputLink");
QTest::newRow("plainHttps") << QStringLiteral("test-validplainlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttps") << QStringLiteral("test-validrichlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("plainWww") << QStringLiteral("test-validplainwwwlink-event.json") << QUrl("www.example.org"_ls);
QTest::newRow("multipleHttps") << QStringLiteral("test-multiplelink-event.json") << QUrl("www.example.org"_ls);
}
void LinkPreviewerTest::linkPreviewsMatch()
{
QFETCH(QString, eventSource);
QFETCH(QUrl, testOutputLink);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), testOutputLink);
}
void LinkPreviewerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("eventSource");
QTest::newRow("mxc") << QStringLiteral("test-invalidmxclink-event.json");
QTest::newRow("matrixTo") << QStringLiteral("test-invalidmatrixtolink-event.json");
QTest::newRow("noSpace") << QStringLiteral("test-invalidnospacelink-event.json");
}
void LinkPreviewerTest::linkPreviewsReject()
{
QFETCH(QString, eventSource);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(linkPreviewer.empty(), true);
QCOMPARE(linkPreviewer.url(), QUrl());
}
void LinkPreviewerTest::editedLink()
{
room->syncNewEvents(QStringLiteral("test-linkpreviewerintial-sync.json"));
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto linkPreviewer = LinkPreviewer(room, event);
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), QUrl("https://kde.org"_ls));
room->syncNewEvents(QStringLiteral("test-linkpreviewerreplace-sync.json"));
QCOMPARE(linkPreviewer.empty(), true);
QCOMPARE(linkPreviewer.url(), QUrl());
}
QTEST_MAIN(LinkPreviewerTest)
#include "linkpreviewertest.moc"

View File

@@ -1,6 +1,7 @@
// 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 <Quotient/events/event.h>
#include <Quotient/syncdata.h>
#include "neochatroom.h"
@@ -38,4 +39,17 @@ public:
}
}
};
template<Quotient::EventClass EventT>
inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFileName)
{
if (!eventFileName.isEmpty()) {
QFile testEventFile;
testEventFile.setFileName(QLatin1String(DATA_DIR) + u'/' + eventFileName);
testEventFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
return Quotient::loadEvent<EventT>(testSyncJson);
}
return nullptr;
}
}

View File

@@ -64,11 +64,6 @@ private Q_SLOTS:
void receiveRichEdited();
void receiveLineSeparator();
void receiveRichCodeUrl();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
};
void TextHandlerTest::initTestCase()
@@ -523,53 +518,6 @@ void TextHandlerTest::receiveLineSeparator()
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
}
void TextHandlerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org"_ls)});
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org"_ls)});
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org"_ls)});
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
<< QList<QUrl>({
QUrl("https://kde.org"_ls),
QUrl("www.example.org"_ls),
});
}
void TextHandlerTest::linkPreviewsMatch()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF") << QList<QUrl>();
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org") << QList<QUrl>();
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org") << QList<QUrl>();
}
void TextHandlerTest::linkPreviewsReject()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::receiveRichCodeUrl()
{
auto input = QStringLiteral("<code>https://kde.org</code>");

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

@@ -7,10 +7,7 @@
#include <qt6keychain/keychain.h>
#include <KLocalizedString>
#include <KWindowConfig>
#include <QFile>
#include <QFileInfo>
#include <QGuiApplication>
#include <QNetworkProxy>
#include <QQuickTextDocument>
@@ -28,13 +25,11 @@
#include <Quotient/eventstats.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/user.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "roommanager.h"
#include "windowcontroller.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
@@ -77,8 +72,7 @@ Controller::Controller(QObject *parent)
});
#ifndef Q_OS_WINDOWS
// Setup Unix signal handlers
const auto unixExitHandler = [](int /*sig*/) -> void {
const auto unixExitHandler = [](int) -> void {
QCoreApplication::quit();
};
@@ -104,7 +98,7 @@ Controller::Controller(QObject *parent)
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
if (m_accountRegistry.size() > oldAccountCount) {
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
connect(connection, &NeoChatConnection::syncDone, this, [this, connection]() {
connect(connection, &NeoChatConnection::syncDone, this, [connection]() {
NotificationsManager::instance().handleNotifications(connection);
});
connectSingleShot(connection, &NeoChatConnection::syncDone, this, [this, connection] {
@@ -144,7 +138,7 @@ void Controller::addConnection(NeoChatConnection *c)
c->setLazyLoading(true);
connect(c, &NeoChatConnection::syncDone, this, [this, c] {
connect(c, &NeoChatConnection::syncDone, this, [c] {
c->sync(30000);
c->saveState();
});
@@ -152,12 +146,6 @@ void Controller::addConnection(NeoChatConnection *c)
dropConnection(c);
});
connect(c, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
if (job->error() == BaseJob::UserConsentRequired) {
Q_EMIT userConsentRequired(job->errorUrl());
}
});
c->sync();
Q_EMIT connectionAdded(c);
@@ -174,18 +162,13 @@ void Controller::dropConnection(NeoChatConnection *c)
void Controller::invokeLogin()
{
const auto accounts = SettingsGroup("Accounts"_ls).childGroups();
QString id = NeoChatConfig::self()->activeConnection();
for (const auto &accountId : accounts) {
AccountSettings account{accountId};
m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged();
if (id.isEmpty()) {
// handle case where the account config is empty
id = accountId;
}
if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, id, this, accessTokenLoadingJob](QKeychain::Job *) {
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId};
QString accessToken;
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
@@ -195,28 +178,11 @@ void Controller::invokeLogin()
}
auto connection = new NeoChatConnection(account.homeserver());
connect(connection, &NeoChatConnection::connected, this, [this, connection, id] {
connect(connection, &NeoChatConnection::connected, this, [this, connection] {
connection->loadState();
addConnection(connection);
m_accountsLoading.removeAll(connection->userId());
Q_EMIT accountsLoadingChanged();
if (connection->userId() == id) {
setActiveConnection(connection);
}
});
connect(connection, &NeoChatConnection::loginError, this, [this, connection](const QString &error, const QString &) {
if (error == "Unrecognised access token"_ls) {
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"), {});
connection->logout(false);
} else if (error == "Connection closed"_ls) {
Q_EMIT errorOccured(i18n("Login Failed: %1", error), {});
// Failed due to network connection issue. This might happen when the homeserver is
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
// connect to the homeserver. In this case, we don't want to do logout().
} else {
Q_EMIT errorOccured(i18n("Login Failed: %1", error), {});
connection->logout(true);
}
});
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
Q_EMIT errorOccured(i18n("Network Error: %1", error), {});
@@ -325,24 +291,16 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
}
m_connection = connection;
if (connection != nullptr) {
NeoChatConfig::self()->setActiveConnection(connection->userId());
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
}
});
} else {
NeoChatConfig::self()->setActiveConnection(QString());
}
NeoChatConfig::self()->save();
Q_EMIT activeConnectionChanged();
}
void Controller::saveWindowGeometry()
{
WindowController::instance().saveGeometry();
}
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
{
// HACK: Workaround bug QTBUG 93281

View File

@@ -134,9 +134,5 @@ Q_SIGNALS:
void connectionAdded(NeoChatConnection *connection);
void connectionDropped(NeoChatConnection *connection);
void activeConnectionChanged();
void userConsentRequired(QUrl url);
void accountsLoadingChanged();
public Q_SLOTS:
void saveWindowGeometry();
};

View File

@@ -355,7 +355,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (e.repeatsState()) {
auto text = i18n("reinvited %1 to the room", subjectName);
if (!e.reason().isEmpty()) {
text += i18nc("Optional reason for an invitation", ": %1") + e.reason().toHtmlEscaped();
text += i18nc("Optional reason for an invitation", ": %1") + (prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
}
return text;
}
@@ -379,7 +379,9 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (!e.newDisplayName()) {
text = i18nc("their refers to a singular user", "cleared their display name");
} else {
text = i18nc("their refers to a singular user", "changed their display name to %1", e.newDisplayName()->toHtmlEscaped());
text = i18nc("their refers to a singular user",
"changed their display name to %1",
prettyPrint ? e.newDisplayName()->toHtmlEscaped() : *e.newDisplayName());
}
}
if (e.isAvatarUpdate()) {
@@ -415,7 +417,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (e.reason().isEmpty()) {
return i18n("banned %1 from the room", subjectName);
} else {
return i18n("banned %1 from the room: %2", subjectName, e.reason().toHtmlEscaped());
return i18n("banned %1 from the room: %2", subjectName, prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
}
} else {
return i18n("self-banned from the room");
@@ -431,8 +433,8 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
[](const RoomCanonicalAliasEvent &e) {
return (e.alias().isEmpty()) ? i18n("cleared the room main alias") : i18n("set the room main alias to: %1", e.alias());
},
[](const RoomNameEvent &e) {
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", e.name().toHtmlEscaped());
[prettyPrint](const RoomNameEvent &e) {
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", prettyPrint ? e.name().toHtmlEscaped() : e.name());
},
[prettyPrint, stripNewlines](const RoomTopicEvent &e) {
return (e.topic().isEmpty()) ? i18n("cleared the topic")
@@ -447,14 +449,15 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
[](const EncryptionEvent &) {
return i18n("activated End-to-End Encryption");
},
[](const RoomCreateEvent &e) {
return e.isUpgrade() ? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped())
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped());
[prettyPrint](const RoomCreateEvent &e) {
return e.isUpgrade()
? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()))
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()));
},
[](const RoomPowerLevelsEvent &) {
return i18nc("'power level' means permission level", "changed the power levels for this room");
},
[](const StateEvent &e) {
[prettyPrint](const StateEvent &e) {
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
return i18n("changed the server access control lists for this room");
}
@@ -471,7 +474,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
return e.contentJson()["description"_ls].toString();
}
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
},
[](const PollStartEvent &e) {
return e.question();
@@ -774,43 +777,6 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
return mediaInfo;
}
QSharedPointer<LinkPreviewer> EventHandler::getLinkPreviewer() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_room set to nullptr.";
return nullptr;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_event set to nullptr.";
return nullptr;
}
if (!m_event->is<RoomMessageEvent>()) {
return nullptr;
}
QString text;
auto event = eventCast<const RoomMessageEvent>(m_event);
if (event->hasTextContent()) {
auto textContent = static_cast<const EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
TextHandler textHandler;
textHandler.setData(text);
QList<QUrl> links = textHandler.getLinkPreviews();
if (links.size() > 0) {
return QSharedPointer<LinkPreviewer>(new LinkPreviewer(nullptr, m_room, links.size() > 0 ? links[0] : QUrl()));
} else {
return nullptr;
}
}
bool EventHandler::hasReply() const
{
if (m_event == nullptr) {

View File

@@ -231,16 +231,6 @@ public:
*/
QVariantMap getMediaInfo() const;
/**
* @brief Return a LinkPreviewer object for the event.
*
* A nullptr will be returned for any event that doesn't have any links so the
* return should be null checked and an empty LinkPreviewer provided if null.
*
* @sa LinkPreviewer
*/
QSharedPointer<LinkPreviewer> getLinkPreviewer() const;
/**
* @brief Whether the event is a reply to another in the timeline.
*/

View File

@@ -3,25 +3,37 @@
#include "linkpreviewer.h"
#include "controller.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/content-repo.h>
#include <Quotient/events/roommessageevent.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "utils.h"
using namespace Quotient;
LinkPreviewer::LinkPreviewer(QObject *parent, const NeoChatRoom *room, const QUrl &url)
: QObject(parent)
LinkPreviewer::LinkPreviewer(const NeoChatRoom *room, const Quotient::RoomMessageEvent *event)
: QObject(nullptr)
, m_currentRoom(room)
, m_event(event)
, m_loaded(false)
, m_url(url)
, m_url(linkPreview(event))
{
loadUrlPreview();
if (m_currentRoom) {
connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
if (m_event != nullptr && m_currentRoom != nullptr) {
loadUrlPreview();
connect(m_currentRoom, &NeoChatRoom::urlPreviewEnabledChanged, this, &LinkPreviewer::loadUrlPreview);
// Make sure that we react to edits
connect(m_currentRoom, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
if (m_event->id() == newEvent->id()) {
m_event = eventCast<const Quotient::RoomMessageEvent>(newEvent);
m_url = linkPreview(m_event);
Q_EMIT urlChanged();
loadUrlPreview();
}
});
}
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
}
@@ -51,15 +63,6 @@ QUrl LinkPreviewer::url() const
return m_url;
}
void LinkPreviewer::setUrl(QUrl url)
{
if (url != m_url) {
m_url = url;
urlChanged();
loadUrlPreview();
}
}
void LinkPreviewer::loadUrlPreview()
{
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
@@ -98,4 +101,38 @@ bool LinkPreviewer::empty() const
return m_url.isEmpty();
}
QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
{
if (event == nullptr) {
return {};
}
QString text;
if (event->hasTextContent()) {
auto textContent = static_cast<const Quotient::EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
auto data = text.remove(TextRegex::removeRichReply);
auto linksMatch = TextRegex::url.globalMatch(data);
while (linksMatch.hasNext()) {
auto link = linksMatch.next().captured();
if (!link.contains(QStringLiteral("matrix.to"))) {
return QUrl(link);
}
}
return {};
}
bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
{
return !linkPreview(event).isEmpty();
}
#include "moc_linkpreviewer.cpp"

View File

@@ -7,6 +7,11 @@
#include <QQmlEngine>
#include <QUrl>
namespace Quotient
{
class RoomMessageEvent;
}
class NeoChatRoom;
/**
@@ -25,7 +30,7 @@ class LinkPreviewer : public QObject
/**
* @brief The URL to get the preview for.
*/
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(QUrl url READ url NOTIFY urlChanged)
/**
* @brief Whether the preview information has been loaded.
@@ -55,18 +60,25 @@ class LinkPreviewer : public QObject
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
public:
explicit LinkPreviewer(QObject *parent = nullptr, const NeoChatRoom *room = nullptr, const QUrl &url = {});
explicit LinkPreviewer(const NeoChatRoom *room = nullptr, const Quotient::RoomMessageEvent *event = nullptr);
[[nodiscard]] QUrl url() const;
void setUrl(QUrl);
[[nodiscard]] bool loaded() const;
[[nodiscard]] QString title() const;
[[nodiscard]] QString description() const;
[[nodiscard]] QUrl imageSource() const;
[[nodiscard]] bool empty() const;
/**
* @brief Whether the given event has at least 1 pre-viewable link.
*
* A link is only pre-viewable if it is http, https or something starting with www.
*/
static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
private:
const NeoChatRoom *m_currentRoom = nullptr;
const NeoChatRoom *m_currentRoom;
const Quotient::RoomMessageEvent *m_event;
bool m_loaded;
QString m_title = QString();
@@ -76,6 +88,14 @@ private:
void loadUrlPreview();
/**
* @brief Return the link to be previewed from the given event.
*
* This function is designed to give only links that should be previewed so
* http, https or something starting with www. The first valid link is returned.
*/
static QUrl linkPreview(const Quotient::RoomMessageEvent *event);
Q_SIGNALS:
void loadedChanged();
void titleChanged();

View File

@@ -12,7 +12,6 @@
#include <KLocalizedString>
#include "controller.h"
#include "neochatconnection.h"
#include <Quotient/connection.h>

View File

@@ -4,7 +4,6 @@
#include "actionsmodel.h"
#include "chatbarcache.h"
#include "controller.h"
#include "neochatroom.h"
#include "roommanager.h"
#include <Quotient/events/roommemberevent.h>

View File

@@ -6,10 +6,8 @@
#include <QImage>
#include <QMimeDatabase>
#include "controller.h"
#include "emojimodel.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/account-data.h>
#include <Quotient/csapi/content-repo.h>

View File

@@ -6,9 +6,8 @@
#include <QAbstractListModel>
#include <QQmlEngine>
#include <QRegularExpression>
#include <memory>
class NeoChatConnection;
#include "neochatconnection.h"
struct CustomEmoji {
QString name; // with :semicolons:

View File

@@ -3,7 +3,6 @@
#include "devicesmodel.h"
#include "controller.h"
#include "jobs/neochatdeletedevicejob.h"
#include <QDateTime>

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "messageeventmodel.h"
#include "linkpreviewer.h"
#include "messageeventmodel_logging.h"
#include "neochatconfig.h"
@@ -22,6 +23,7 @@
#include "eventhandler.h"
#include "events/pollevent.h"
#include "models/reactionmodel.h"
#include "texthandler.h"
using namespace Quotient;
@@ -237,6 +239,10 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
});
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
const RoomMessageEvent *message = eventCast<const RoomMessageEvent>(newEvent);
if (message != nullptr) {
createEventObjects(message);
}
});
connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) {
if (eventId.isEmpty()) { // How did we get here?
@@ -720,14 +726,14 @@ void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *eve
{
auto eventId = event->id();
EventHandler eventHandler;
eventHandler.setRoom(m_currentRoom);
eventHandler.setEvent(event);
if (auto linkPreviewer = eventHandler.getLinkPreviewer()) {
m_linkPreviewers[eventId] = linkPreviewer;
if (m_linkPreviewers.contains(eventId)) {
if (!LinkPreviewer::hasPreviewableLinks(event)) {
m_linkPreviewers.remove(eventId);
}
} else {
m_linkPreviewers.remove(eventId);
if (LinkPreviewer::hasPreviewableLinks(event)) {
m_linkPreviewers[eventId] = QSharedPointer<LinkPreviewer>(new LinkPreviewer(m_currentRoom, event));
}
}
// ReactionModel handles updates to add and remove reactions, we only need to

View File

@@ -11,7 +11,6 @@
#include <Quotient/csapi/pushrules.h>
#include <Quotient/jobs/basejob.h>
#include "controller.h"
#include "neochatconfig.h"
#include <KLazyLocalizedString>

View File

@@ -21,6 +21,8 @@ class User;
class ReactionModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**

View File

@@ -3,15 +3,12 @@
#include "roomlistmodel.h"
#include "controller.h"
#include "eventhandler.h"
#include "neochatconfig.h"
#include "neochatroom.h"
#include "roommanager.h"
#include "spacehierarchycache.h"
#include <Quotient/user.h>
#include <QDebug>
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
@@ -23,7 +20,6 @@
#include <KLocalizedString>
#include <QGuiApplication>
#include <utility>
using namespace Quotient;
@@ -349,7 +345,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
return m_categoryVisibility.value(data(index, CategoryRole).toInt(), true);
}
if (role == SubtitleTextRole) {
if (room->lastEventIsSpoiler()) {
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
return QString();
}
EventHandler eventHandler;

View File

@@ -3,8 +3,6 @@
#pragma once
#include <Quotient/events/roomevent.h>
#include <QAbstractListModel>
#include <QQmlEngine>

View File

@@ -3,8 +3,6 @@
#include "serverlistmodel.h"
#include "controller.h"
#include <Quotient/connection.h>
#include <QDebug>
@@ -13,6 +11,8 @@
#include <KConfigGroup>
#include <KSharedConfig>
#include "neochatconnection.h"
ServerListModel::ServerListModel(QObject *parent)
: QAbstractListModel(parent)
{

View File

@@ -7,7 +7,7 @@
#include <Quotient/jobs/basejob.h>
#include <Quotient/room.h>
#include "controller.h"
#include "neochatconnection.h"
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
: QAbstractItemModel(parent)

View File

@@ -3,7 +3,7 @@
#include "spacetreeitem.h"
#include "controller.h"
#include "neochatconnection.h"
SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection,
SpaceTreeItem *parent,

View File

@@ -181,6 +181,7 @@ Name[eu]=Gonbidapen berria
Name[fi]=Uusi kutsu
Name[fr]=Nouvelle invitation
Name[gl]=Novo convite
Name[hu]=Új meghívó
Name[ia]=Nove invitation
Name[id]=Undangan Baru
Name[ie]=Nov invitation
@@ -217,6 +218,7 @@ Comment[eu]=Gela baterako gonbidapen berri bat dago
Comment[fi]=Uusi kutsu huoneeseen
Comment[fr]=Il y a une nouvelle invitation dans un salon.
Comment[gl]=Tes un novo convite para unha sala.
Comment[hu]=Új meghívó érkezett egy szobába
Comment[ia]=Il ha un nove invitation a un sala
Comment[id]=Ada undangan baru ke sebuah ruangan
Comment[ie]=Vu have un nov invitation a un chambre

View File

@@ -11,9 +11,6 @@
<entry name="OpenRoom" type="String">
<label>Latest opened room</label>
</entry>
<entry name="ActiveConnection" type="String">
<label>Latest active connection</label>
</entry>
<entry name="ColorScheme" type="String">
<label>Color scheme</label>
</entry>

View File

@@ -44,6 +44,11 @@ NeoChatConnection::NeoChatConnection(QObject *parent)
connect(this, &NeoChatConnection::networkError, this, [this]() {
setIsOnline(false);
});
connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
if (job->error() == BaseJob::UserConsentRequired) {
Q_EMIT userConsentRequired(job->errorUrl());
}
});
}
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)

View File

@@ -99,6 +99,7 @@ Q_SIGNALS:
void labelChanged();
void isOnlineChanged();
void passwordStatus(NeoChatConnection::PasswordStatus status);
void userConsentRequired(QUrl url);
private:
bool m_isOnline = true;

View File

@@ -4,15 +4,10 @@
#include "neochatroom.h"
#include <QFileInfo>
#include <QGuiApplication>
#include <QMetaObject>
#include <QMimeDatabase>
#include <QPalette>
#include <QTemporaryFile>
#include <QTextDocument>
#include <QMediaMetaData>
#include <QMediaPlayer>
#include <QMimeDatabase>
#include <QTemporaryFile>
#include <Quotient/jobs/basejob.h>
#include <Quotient/user.h>
@@ -35,13 +30,11 @@
#include <Quotient/events/roommemberevent.h>
#include <Quotient/events/roompowerlevelsevent.h>
#include <Quotient/events/simplestateevents.h>
#include <Quotient/events/stickerevent.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include "chatbarcache.h"
#include "clipboard.h"
#include "controller.h"
#include "eventhandler.h"
#include "events/joinrulesevent.h"
#include "events/pollevent.h"
@@ -53,8 +46,6 @@
#include "urlhelper.h"
#include "utils.h"
#include <KConfig>
#include <KConfigGroup>
#ifndef Q_OS_ANDROID
#include <KIO/Job>
#include <KIO/JobTracker>

View File

@@ -21,7 +21,6 @@
#endif
#include "controller.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "roommanager.h"

View File

@@ -56,6 +56,7 @@ Comment[eu]=Bilatu gelak NeoChat-en
Comment[fi]=Etsi huoneita NeoChatissä
Comment[fr]=Trouver des salons dans NeoChat
Comment[gl]=Atopa salas en NeoChat.
Comment[hu]=Szobák keresése a NeoChatben
Comment[ia]=Trova salas in NeoChat
Comment[id]=Cari ruangan di NeoChat
Comment[ie]=Trovar chambres in NeoChat

View File

@@ -86,7 +86,7 @@ Kirigami.ApplicationWindow {
Timer {
id: saveWindowGeometryTimer
interval: 1000
onTriggered: Controller.saveWindowGeometry()
onTriggered: WindowController.saveGeometry()
}
Connections {
@@ -94,7 +94,7 @@ Kirigami.ApplicationWindow {
enabled: false // Disable on startup to avoid writing wrong values if the window is hidden
target: root
function onClosing() { Controller.saveWindowGeometry(); }
function onClosing() { WindowController.saveGeometry(); }
function onWidthChanged() { saveWindowGeometryTimer.restart(); }
function onHeightChanged() { saveWindowGeometryTimer.restart(); }
function onXChanged() { saveWindowGeometryTimer.restart(); }
@@ -300,12 +300,6 @@ Kirigami.ApplicationWindow {
function onErrorOccured(error, detail) {
showPassiveNotification(detail.length > 0 ? i18n("%1: %2", error, detail) : error);
}
function onUserConsentRequired(url) {
let consent = consentSheetComponent.createObject(QQC2.ApplicationWindow.overlay)
consent.url = url
consent.open()
}
}
Connections {
@@ -341,6 +335,11 @@ Kirigami.ApplicationWindow {
title: i18nc("@title:window", "Session Verification")
});
}
function onUserConsentRequired(url) {
let consent = consentSheetComponent.createObject(QQC2.ApplicationWindow.overlay)
consent.url = url
consent.open()
}
}
Component {

View File

@@ -5,11 +5,10 @@
#include "roommanager.h"
#include "chatbarcache.h"
#include "controller.h"
#include "enums/delegatetype.h"
#include "models/messageeventmodel.h"
#include "models/timelinemodel.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include <KLocalizedString>

View File

@@ -13,7 +13,6 @@
#include "chatdocumenthandler.h"
#include "enums/delegatetype.h"
#include "models/mediamessagefiltermodel.h"
#include "models/messageeventmodel.h"
#include "models/messagefiltermodel.h"
#include "models/timelinemodel.h"

View File

@@ -5,7 +5,6 @@
#include <QDBusMetaType>
#include "controller.h"
#include "neochatroom.h"
#include "roommanager.h"
#include "windowcontroller.h"

View File

@@ -6,7 +6,6 @@
#include <Quotient/csapi/space_hierarchy.h>
#include <Quotient/qt_connection_util.h>
#include "controller.h"
#include "neochatroom.h"
using namespace Quotient;

View File

@@ -493,18 +493,4 @@ QString TextHandler::linkifyUrls(QString stringIn)
return stringIn;
}
QList<QUrl> TextHandler::getLinkPreviews()
{
auto data = m_data.remove(TextRegex::removeRichReply);
auto linksMatch = TextRegex::url.globalMatch(data);
QList<QUrl> links;
while (linksMatch.hasNext()) {
auto link = linksMatch.next().captured();
if (!link.contains(QStringLiteral("matrix.to"))) {
links += QUrl(link);
}
}
return links;
}
#include "moc_texthandler.cpp"

View File

@@ -11,29 +11,9 @@
#include "neochatroom.h"
namespace TextRegex
namespace Quotient
{
static const QRegularExpression endTagType{QStringLiteral("(>| )")};
static const QRegularExpression attributeData{QStringLiteral("['\"](.*?)['\"]")};
static const QRegularExpression removeReply{QStringLiteral("> <.*?>.*?\\n\\n"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression removeRichReply{QStringLiteral("<mx-reply>.*?</mx-reply>"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression codePill{QStringLiteral("<pre><code[^>]*>(.*?)</code></pre>"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression userPill{QStringLiteral("(<a href=\"https://matrix.to/#/@.*?:.*?\">.*?</a>)"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression blockQuote{QStringLiteral("<blockquote>\n?(?:<p>)?(.*?)(?:</p>)?\n?</blockquote>"),
QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression strikethrough{QStringLiteral("<del>(.*?)</del>"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression mxcImage{QStringLiteral(R"AAA(<img(.*?)src="mxc:\/\/(.*?)\/(.*?)"(.*?)>)AAA")};
static const QRegularExpression plainUrl(
QStringLiteral(
R"(<a.*?<\/a>(*SKIP)(*F)|\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp):(//)?\w|(magnet|matrix):)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
static const QRegularExpression
url(QStringLiteral(R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|https?:(//)?\w)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
static const QRegularExpression emailAddress(QStringLiteral(R"(<a.*?<\/a>(*SKIP)(*F)|\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
static const QRegularExpression mxId(QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
class RoomMessageEvent;
}
/**
@@ -114,14 +94,6 @@ public:
*/
QString handleRecievePlainText(Qt::TextFormat inputFormat = Qt::PlainText, const bool &stripNewlines = false);
/**
* @brief Return a list of links that can be previewed.
*
* This function is designed to give only links that should be previewed so
* http, https or something starting with www.
*/
QList<QUrl> getLinkPreviews();
private:
QString m_data;

View File

@@ -4,6 +4,7 @@
#include <QColor>
#include <QGuiApplication>
#include <QPalette>
#include <QRegularExpression>
namespace Utils
{
@@ -21,3 +22,28 @@ inline QColor getUserColor(qreal hueF)
return QColor::fromHslF(hueF, 1, -0.7 * lightness + 0.9, 1);
}
}
namespace TextRegex
{
static const QRegularExpression endTagType{QStringLiteral("(>| )")};
static const QRegularExpression attributeData{QStringLiteral("['\"](.*?)['\"]")};
static const QRegularExpression removeReply{QStringLiteral("> <.*?>.*?\\n\\n"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression removeRichReply{QStringLiteral("<mx-reply>.*?</mx-reply>"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression codePill{QStringLiteral("<pre><code[^>]*>(.*?)</code></pre>"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression userPill{QStringLiteral("(<a href=\"https://matrix.to/#/@.*?:.*?\">.*?</a>)"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression blockQuote{QStringLiteral("<blockquote>\n?(?:<p>)?(.*?)(?:</p>)?\n?</blockquote>"),
QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression strikethrough{QStringLiteral("<del>(.*?)</del>"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression mxcImage{QStringLiteral(R"AAA(<img(.*?)src="mxc:\/\/(.*?)\/(.*?)"(.*?)>)AAA")};
static const QRegularExpression plainUrl(
QStringLiteral(
R"(<a.*?<\/a>(*SKIP)(*F)|\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp):(//)?\w|(magnet|matrix):)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
static const QRegularExpression
url(QStringLiteral(R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|https?:(//)?\w)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
static const QRegularExpression emailAddress(QStringLiteral(R"(<a.*?<\/a>(*SKIP)(*F)|\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
static const QRegularExpression mxId(QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
}

View File

@@ -52,7 +52,7 @@ public:
/**
* @brief Save the current window geometry.
*/
void saveGeometry();
Q_INVOKABLE void saveGeometry();
/**
* @brief Show the window and raise to the top.