From 1d5bf56cf9dc8aea4aa849ef5f0f580c1eae4cdd Mon Sep 17 00:00:00 2001 From: Thulinma Date: Thu, 16 Sep 2021 01:41:55 +0200 Subject: [PATCH] Improvements for linking to events - Fixes scrolling to an event not being reliable - Adds new /goto command that can open URLs, go to events, or go to message indexes. - Refactored ChatPage::handleMatrixUri() to contain the handling originally in Nheko::openLink(), and makes it return a boolean based on whether the URL was handled internally or not. --- resources/qml/MessageView.qml | 5 ++- src/ChatPage.cpp | 71 +++++++++++++++++++++++++++++----- src/ChatPage.h | 4 +- src/timeline/InputBar.cpp | 17 ++++++++ src/timeline/TimelineModel.cpp | 16 +++++++- src/ui/NhekoGlobalObject.cpp | 44 +-------------------- 6 files changed, 101 insertions(+), 56 deletions(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index d80d274d..e4e58845 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -20,6 +20,9 @@ ScrollView { ListView { id: chat + + displayMarginBeginning: height/2 + displayMarginEnd: height/2 property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding * 2 @@ -276,7 +279,7 @@ ScrollView { } function onScrollToIndex(index) { - chat.positionViewAtIndex(index, ListView.Visible); + chat.positionViewAtIndex(index, ListView.Center); } target: chat.model diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index a07a9654..3887f8b8 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -1233,14 +1233,58 @@ mxidFromSegments(QStringRef sigil, QStringRef mxid) } } -void +bool ChatPage::handleMatrixUri(const QByteArray &uri) { nhlog::ui()->info("Received uri! {}", uri.toStdString()); QUrl uri_{QString::fromUtf8(uri)}; + // Convert matrix.to URIs to proper format + if (uri_.scheme() == "https" && uri_.host() == "matrix.to") { + QString p = uri_.fragment(QUrl::FullyEncoded); + if (p.startsWith("/")) + p.remove(0, 1); + + auto temp = p.split("?"); + QString query; + if (temp.size() >= 2) + query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8()); + + temp = temp.first().split("/"); + auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8()); + QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8()); + if (!identifier.isEmpty()) { + if (identifier.startsWith("@")) { + QByteArray newUri = + "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); + if (!query.isEmpty()) + newUri.append("?" + query.toUtf8()); + return handleMatrixUri(QUrl::fromEncoded(newUri)); + } else if (identifier.startsWith("#")) { + QByteArray newUri = + "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); + if (!eventId.isEmpty()) + newUri.append( + "/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1))); + if (!query.isEmpty()) + newUri.append("?" + query.toUtf8()); + return handleMatrixUri(QUrl::fromEncoded(newUri)); + } else if (identifier.startsWith("!")) { + QByteArray newUri = "matrix:roomid/" + QUrl::toPercentEncoding( + identifier.remove(0, 1)); + if (!eventId.isEmpty()) + newUri.append( + "/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1))); + if (!query.isEmpty()) + newUri.append("?" + query.toUtf8()); + return handleMatrixUri(QUrl::fromEncoded(newUri)); + } + } + } + + // non-matrix URIs are not handled by us, return false if (uri_.scheme() != "matrix") - return; + return false; auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded); if (tempPath.startsWith('/')) @@ -1248,17 +1292,17 @@ ChatPage::handleMatrixUri(const QByteArray &uri) auto segments = tempPath.splitRef('/'); if (segments.size() != 2 && segments.size() != 4) - return; + return false; auto sigil1 = segments[0]; auto mxid1 = mxidFromSegments(sigil1, segments[1]); if (mxid1.isEmpty()) - return; + return false; QString mxid2; if (segments.size() == 4 && segments[2] == "e") { if (segments[3].isEmpty()) - return; + return false; else mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8()); } @@ -1283,12 +1327,13 @@ ChatPage::handleMatrixUri(const QByteArray &uri) if (t && cache::isRoomMember(mxid1.toStdString(), t->roomId().toStdString())) { t->openUserProfile(mxid1); - return; + return true; } emit view_manager_->openGlobalUserProfile(mxid1); } else if (action == "chat") { this->startChat(mxid1); } + return true; } else if (sigil1 == "roomid") { auto joined_rooms = cache::joinedRooms(); auto targetRoomId = mxid1.toStdString(); @@ -1298,13 +1343,15 @@ ChatPage::handleMatrixUri(const QByteArray &uri) view_manager_->rooms()->setCurrentRoom(mxid1); if (!mxid2.isEmpty()) view_manager_->showEvent(mxid1, mxid2); - return; + return true; } } if (action == "join" || action.isEmpty()) { joinRoomVia(targetRoomId, vias); + return true; } + return false; } else if (sigil1 == "r") { auto joined_rooms = cache::joinedRooms(); auto targetRoomAlias = mxid1.toStdString(); @@ -1318,21 +1365,25 @@ ChatPage::handleMatrixUri(const QByteArray &uri) if (!mxid2.isEmpty()) view_manager_->showEvent( QString::fromStdString(roomid), mxid2); - return; + return true; } } } if (action == "join" || action.isEmpty()) { joinRoomVia(mxid1.toStdString(), vias); + return true; } + return false; } + return false; } -void +bool ChatPage::handleMatrixUri(const QUrl &uri) { - handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8()); + return handleMatrixUri( + uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8()); } bool diff --git a/src/ChatPage.h b/src/ChatPage.h index 9cbf2a03..66e4c6ab 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -78,8 +78,8 @@ public: QString currentRoom() const; public slots: - void handleMatrixUri(const QByteArray &uri); - void handleMatrixUri(const QUrl &uri); + bool handleMatrixUri(const QByteArray &uri); + bool handleMatrixUri(const QUrl &uri); void startChat(QString userid); void leaveRoom(const QString &room_id); diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index ece9db62..a6fbab78 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -630,6 +630,23 @@ InputBar::command(QString command, QString args) notice(args, false); } else if (command == "rainbownotice") { notice(args, true); + } else if (command == "goto") { + // Goto has three different modes: + // 1 - Going directly to a given event ID + if (args[0] == '$') { + room->showEvent(args); + return; + } + // 2 - Going directly to a given message index + if (args[0] >= '0' && args[0] <= '9') { + room->showEvent(args); + return; + } + // 3 - Matrix URI handler, as if you clicked the URI + if (ChatPage::instance()->handleMatrixUri(args)) { + return; + } + nhlog::net()->error("Could not resolve goto: {}", args.toStdString()); } } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index e03c32a7..00f6d9df 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1534,11 +1534,25 @@ void TimelineModel::showEvent(QString eventId) { using namespace std::chrono_literals; - if (idToIndex(eventId) != -1) { + // Direct to eventId + if (eventId[0] == '$') { + int idx = idToIndex(eventId); + if (idx == -1) { + nhlog::ui()->warn("Scrolling to event id {}, failed - no known index", + eventId.toStdString()); + return; + } eventIdToShow = eventId; emit scrollTargetChanged(); showEventTimer.start(50ms); + return; } + // to message index + eventId = indexToId(eventId.toInt()); + eventIdToShow = eventId; + emit scrollTargetChanged(); + showEventTimer.start(50ms); + return; } void diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index 9e0d706b..355f187b 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -57,48 +57,8 @@ void Nheko::openLink(QString link) const { QUrl url(link); - if (url.scheme() == "https" && url.host() == "matrix.to") { - // handle matrix.to links internally - QString p = url.fragment(QUrl::FullyEncoded); - if (p.startsWith("/")) - p.remove(0, 1); - - auto temp = p.split("?"); - QString query; - if (temp.size() >= 2) - query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8()); - - temp = temp.first().split("/"); - auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8()); - QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8()); - if (!identifier.isEmpty()) { - if (identifier.startsWith("@")) { - QByteArray uri = - "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); - if (!query.isEmpty()) - uri.append("?" + query.toUtf8()); - ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri)); - } else if (identifier.startsWith("#")) { - QByteArray uri = - "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); - if (!eventId.isEmpty()) - uri.append("/e/" + - QUrl::toPercentEncoding(eventId.remove(0, 1))); - if (!query.isEmpty()) - uri.append("?" + query.toUtf8()); - ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri)); - } else if (identifier.startsWith("!")) { - QByteArray uri = "matrix:roomid/" + - QUrl::toPercentEncoding(identifier.remove(0, 1)); - if (!eventId.isEmpty()) - uri.append("/e/" + - QUrl::toPercentEncoding(eventId.remove(0, 1))); - if (!query.isEmpty()) - uri.append("?" + query.toUtf8()); - ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri)); - } - } - } else { + // Open externally if we couldn't handle it internally + if (!ChatPage::instance()->handleMatrixUri(url)) { QDesktopServices::openUrl(url); } }