Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F7726668
D229.1757756568.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
19 KB
Referenced Files
None
Subscribers
None
D229.1757756568.diff
View Options
diff --git a/src/contents/ui/CreateRoomPage.qml b/src/contents/ui/CreateRoomPage.qml
--- a/src/contents/ui/CreateRoomPage.qml
+++ b/src/contents/ui/CreateRoomPage.qml
@@ -11,6 +11,7 @@
import org.kde.kirigami 2.13 as Kirigami
import '.' as Kazv
+import './matrix-helpers.js' as Helpers
import moe.kazv.mxc.kazv 0.0 as MK
@@ -140,6 +141,7 @@
Layout.fillWidth: true
}
Button {
+ id: addInviteUserButton
objectName: 'addInviteUserButton'
text: l10n.get('create-room-page-action-add-invite')
Layout.fillWidth: true
@@ -180,6 +182,9 @@
onResolved: {
if (success) {
+ if (typeDirect.checked && createRoomPage.inviteUserIds.length === 1) {
+ matrixSdk.addDirectRoom(createRoomPage.inviteUserIds[0], data.roomId);
+ }
showPassiveNotification(l10n.get('create-room-page-success-prompt'));
pageStack.removePage(createRoomPage);
} else {
@@ -187,4 +192,10 @@
}
}
}
+
+ function presetPage(userId) {
+ typeDirect.checked = true;
+ newInviteUserId.text = userId;
+ addInviteUserButton.click();
+ }
}
diff --git a/src/contents/ui/EventView.qml b/src/contents/ui/EventView.qml
--- a/src/contents/ui/EventView.qml
+++ b/src/contents/ui/EventView.qml
@@ -149,6 +149,7 @@
Loader {
sourceComponent: getSource(messageType)
id: loader
+ objectName: 'loader'
anchors.left: eventView.left
anchors.right: eventView.right
anchors.top: eventView.top
diff --git a/src/contents/ui/JoinRoomPage.qml b/src/contents/ui/JoinRoomPage.qml
--- a/src/contents/ui/JoinRoomPage.qml
+++ b/src/contents/ui/JoinRoomPage.qml
@@ -72,4 +72,9 @@
{
joinRoomHandler.call();
}
+
+ function presetPage(roomIdOrAlias, routingServers) {
+ idOrAlias.text = roomIdOrAlias;
+ servers.text = routingServers.join('\n');
+ }
}
diff --git a/src/contents/ui/RoomPage.qml b/src/contents/ui/RoomPage.qml
--- a/src/contents/ui/RoomPage.qml
+++ b/src/contents/ui/RoomPage.qml
@@ -265,4 +265,19 @@
onViewEventHistoryRequested: (eventId) => {
eventHistoryPopupComp.createObject(applicationWindow().overlay, { eventId }).open();
}
+
+ Timer {
+ id: goToEventTimer
+ property var eventId
+ onTriggered: {
+ console.log('goto event');
+ roomTimelineView.goToEvent(eventId);
+ }
+ }
+
+ function delayGoToEvent(eventId, time) {
+ goToEventTimer.interval = time;
+ goToEventTimer.eventId = eventId
+ goToEventTimer.start();
+ }
}
diff --git a/src/contents/ui/event-types/TextTemplate.qml b/src/contents/ui/event-types/TextTemplate.qml
--- a/src/contents/ui/event-types/TextTemplate.qml
+++ b/src/contents/ui/event-types/TextTemplate.qml
@@ -109,7 +109,52 @@
}
}
+ // https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2312-matrix-uri.md#operations-on-matrix-uris
function openLink(link) {
- Qt.openUrlExternally(link);
+ const matrixLink = MK.KazvUtil.matrixLink(link);
+ if (!matrixLink.isValid) {
+ Qt.openUrlExternally(link);
+ return;
+ }
+ const action = matrixLink.action;
+ const servers = matrixLink.routingServers;
+ const joinRoom = (roomId, servers) => {
+ pushJoinRoomPage();
+ pageStack.currentItem.presetPage(roomId, servers);
+ };
+ if (matrixLink.isUser) {
+ const userId = matrixLink.identifiers[0];
+ if (action === 'chat') {
+ // If the room with the user already exists, switch to the room.
+ // Otherwise try to create the room.
+ const directRoomIds = matrixSdk.directRoomIds(userId);
+ if (directRoomIds.length !== 0) {
+ switchToRoomRequested(directRoomIds[0]);
+ } else {
+ pushCreateRoomPage();
+ pageStack.currentItem.presetPage(userId);
+ }
+ } else {
+ mentionUserRequested(userId);
+ }
+ } else if (matrixLink.isEvent) {
+ const roomId = matrixLink.identifiers[0];
+ const eventId = matrixLink.identifiers[1];
+ if (sdkVars.roomList.contains(roomId)) {
+ switchToRoomRequested(roomId);
+ pageStack.currentItem.delayGoToEvent(eventId, 500);
+ } else {
+ joinRoom(roomId, servers);
+ }
+ } else if (matrixLink.isRoom) {
+ const roomIdOrAlias = matrixLink.identifiers[0];
+ // If user has joined the room, open it.
+ // Otherwise try to join it.
+ if (sdkVars.roomList.contains(roomIdOrAlias)) {
+ switchToRoomRequested(roomIdOrAlias);
+ } else {
+ joinRoom(roomIdOrAlias, servers);
+ }
+ }
}
}
diff --git a/src/kazv-util.hpp b/src/kazv-util.hpp
--- a/src/kazv-util.hpp
+++ b/src/kazv-util.hpp
@@ -7,6 +7,8 @@
#pragma once
#include <kazv-defs.hpp>
+#include "matrix-link.hpp"
+
#include <QObject>
#include <QString>
#include <QQmlEngine>
@@ -30,6 +32,7 @@
/// @return The user id for a given matrix link, or the empty string
/// if @c url is not a matrix link pointing to a user
QString matrixLinkUserId(const QString &url) const;
+ MatrixLink *matrixLink(const QString &url) const;
private:
int m_kfQtMajorVersion;
diff --git a/src/kazv-util.cpp b/src/kazv-util.cpp
--- a/src/kazv-util.cpp
+++ b/src/kazv-util.cpp
@@ -37,3 +37,8 @@
}
return QStringLiteral("");
}
+
+MatrixLink *KazvUtil::matrixLink(const QString &url) const
+{
+ return new MatrixLink(QUrl(url));
+}
diff --git a/src/matrix-link.hpp b/src/matrix-link.hpp
--- a/src/matrix-link.hpp
+++ b/src/matrix-link.hpp
@@ -17,14 +17,16 @@
/**
* A link that represents a Matrix object.
*/
-class MatrixLink
+class MatrixLink : public QObject
{
- Q_GADGET
+ Q_OBJECT
+ Q_PROPERTY(bool isValid READ isValid CONSTANT)
Q_PROPERTY(bool isUser READ isUser CONSTANT)
Q_PROPERTY(bool isRoom READ isRoom CONSTANT)
Q_PROPERTY(bool isEvent READ isEvent CONSTANT)
Q_PROPERTY(QStringList identifiers READ identifiers CONSTANT)
Q_PROPERTY(QStringList routingServers READ routingServers CONSTANT)
+ Q_PROPERTY(QString action READ action CONSTANT)
public:
enum Type {
diff --git a/src/matrix-room-list.hpp b/src/matrix-room-list.hpp
--- a/src/matrix-room-list.hpp
+++ b/src/matrix-room-list.hpp
@@ -45,4 +45,5 @@
Q_INVOKABLE MatrixRoom *at(int index) const;
Q_INVOKABLE QString roomIdAt(int index) const;
Q_INVOKABLE MatrixRoom *room(QString roomId) const;
+ Q_INVOKABLE bool contains(QString roomId) const;
};
diff --git a/src/matrix-room-list.cpp b/src/matrix-room-list.cpp
--- a/src/matrix-room-list.cpp
+++ b/src/matrix-room-list.cpp
@@ -192,3 +192,8 @@
userGivenNicknameMapFor(m_client)
);
}
+
+bool MatrixRoomList::contains(QString roomId) const
+{
+ return roomIds().contains(roomId);
+}
diff --git a/src/matrix-sdk.hpp b/src/matrix-sdk.hpp
--- a/src/matrix-sdk.hpp
+++ b/src/matrix-sdk.hpp
@@ -11,10 +11,12 @@
#include <QQmlEngine>
#include <QString>
+#include <string>
#include <memory>
#include <filesystem>
#include <lager/extra/qt.hpp>
+#include <immer/map.hpp>
#include <sdk-model.hpp>
#include <random-generator.hpp>
@@ -113,6 +115,8 @@
// Return true if the spec version of server in the range [minVer, maxVer]
Q_INVOKABLE bool checkSpecVersionRange(QString minVer, QString maxVer) const;
+ Q_INVOKABLE QStringList directRoomIds(QString userId) const;
+
Kazv::RandomInterface &randomGenerator() const;
private:
@@ -300,6 +304,8 @@
*/
MatrixPromise *getSpecVersions();
+ MatrixPromise *addDirectRoom(const QString &userId, const QString &roomId);
+
private:
MatrixPromise *sendAccountDataImpl(Kazv::Event event);
diff --git a/src/matrix-sdk.cpp b/src/matrix-sdk.cpp
--- a/src/matrix-sdk.cpp
+++ b/src/matrix-sdk.cpp
@@ -459,6 +459,17 @@
}) != specVersions().end();
}
+QStringList MatrixSdk::directRoomIds(QString userId) const
+{
+ auto content = m_d->clientOnSecondaryRoot.
+ accountData().get()["m.direct"].content().get();
+ auto roomIds = QStringList{};
+ for (auto i : content[userId.toStdString()]) {
+ roomIds.push_back(QString::fromStdString(i.get<std::string>()));
+ }
+ return roomIds;
+}
+
std::string MatrixSdk::validateHomeserverUrl(const QString &url)
{
if (url.isEmpty()) {
@@ -887,6 +898,11 @@
return new MatrixPromise(m_d->clientOnSecondaryRoot.getVersions(LAGER_QT(serverUrl).get().toStdString()));
}
+MatrixPromise *MatrixSdk::addDirectRoom(const QString &userId, const QString &roomId)
+{
+ return new MatrixPromise(m_d->clientOnSecondaryRoot.addDirectRoom(userId.toStdString(), roomId.toStdString()));
+}
+
void MatrixSdk::setUserDataDir(const std::string &userDataDir)
{
m_d->userDataDir = userDataDir;
diff --git a/src/tests/matrix-room-list-test.cpp b/src/tests/matrix-room-list-test.cpp
--- a/src/tests/matrix-room-list-test.cpp
+++ b/src/tests/matrix-room-list-test.cpp
@@ -34,6 +34,7 @@
void testSortedWithTag();
void testFilter();
void testPriority();
+ void testExist();
};
static auto tagEvent = Event{R"({
@@ -230,6 +231,19 @@
QCOMPARE(roomList->roomIdAt(1), u"!favourite:tusooa.xyz"_s);
}
+void MatrixRoomListTest::testExist()
+{
+ auto model = makeTestModel();
+ RoomModel room;
+ room.roomId = "!test:tusooa.xyz";
+ model.client.roomList.rooms = model.client.roomList.rooms.set(room.roomId, room);
+
+ std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
+ auto roomList = toUniquePtr(sdk->roomList());
+ QVERIFY(roomList->contains(u"!test:tusooa.xyz"_s));
+ QVERIFY(!roomList->contains(u"!notexist:tusooa.xyz"_s));
+}
+
QTEST_MAIN(MatrixRoomListTest)
#include "matrix-room-list-test.moc"
diff --git a/src/tests/matrix-sdk-test.cpp b/src/tests/matrix-sdk-test.cpp
--- a/src/tests/matrix-sdk-test.cpp
+++ b/src/tests/matrix-sdk-test.cpp
@@ -31,6 +31,7 @@
void testSetDeviceTrustLevel();
void testValidateHomeserverUrl();
void testSpecVersion();
+ void testDirectRoomIds();
};
void MatrixSdkTest::testDevicesOfUser()
@@ -108,6 +109,26 @@
QVERIFY(!sdk->checkSpecVersionRange(u"v1.1"_s, u"v1.2"_s));
}
+void MatrixSdkTest::testDirectRoomIds()
+{
+ auto model = makeTestModel();
+ auto j = R"({
+ "type": "m.direct",
+ "content": {
+ "@mew:example.org": [
+ "!somewhere1:example.org",
+ "!somewhere2:example.org"
+ ]
+ }
+ })"_json;
+ model.client.accountData = model.client.accountData.set("m.direct", j);
+ std::unique_ptr<MatrixSdk> sdk{new MatrixSdk(model, /* testing = */ true)};
+ const auto rooms = QStringList{u"!somewhere1:example.org"_s, u"!somewhere2:example.org"_s};
+ const auto directRoomIds = sdk->directRoomIds(u"@mew:example.org"_s);
+ QCOMPARE(QSet(directRoomIds.begin(), directRoomIds.end()),
+ QSet(rooms.begin(), rooms.end()));
+}
+
QTEST_MAIN(MatrixSdkTest)
#include "matrix-sdk-test.moc"
diff --git a/src/tests/quick-tests/test-helpers/MatrixSdkMock.qml b/src/tests/quick-tests/test-helpers/MatrixSdkMock.qml
--- a/src/tests/quick-tests/test-helpers/MatrixSdkMock.qml
+++ b/src/tests/quick-tests/test-helpers/MatrixSdkMock.qml
@@ -26,6 +26,10 @@
property var joinRoom: mockHelper.promise()
property var sendAccountData: mockHelper.promise()
property var login: mockHelper.noop()
+ property var addDirectRoom: mockHelper.promise([
+ 'userId',
+ 'roomId'
+ ])
property var sessions: []
@@ -52,4 +56,14 @@
function checkSpecVersion (ver) {
return !!specVersions.includes(ver);
}
+
+ function directRoomIds(userId) {
+ const directRoomMap = {
+ "@someonewithchat:example.org": ["!directroom:example.org"]
+ };
+ if (!directRoomMap[userId]) {
+ return [];
+ }
+ return directRoomMap[userId];
+ }
}
diff --git a/src/tests/quick-tests/tst_CreateRoomPage.qml b/src/tests/quick-tests/tst_CreateRoomPage.qml
--- a/src/tests/quick-tests/tst_CreateRoomPage.qml
+++ b/src/tests/quick-tests/tst_CreateRoomPage.qml
@@ -114,6 +114,7 @@
preset: MK.MatrixSdk.TrustedPrivateChat,
encrypted: true,
}));
+ tryVerify(() => item.matrixSdk.addDirectRoom.calledTimes() === 0, 1000);
}
function test_createRoomDirectInviteOne() {
@@ -146,6 +147,12 @@
preset: MK.MatrixSdk.TrustedPrivateChat,
encrypted: true,
}));
+ item.matrixSdk.createRoom.lastRetVal().resolve(true, { 'roomId': '!somewhere:example.org' });
+ tryVerify(() => item.matrixSdk.addDirectRoom.calledTimes() === 1, 1000);
+ verify(JsHelpers.deepEqual(item.matrixSdk.addDirectRoom.lastArgs(), {
+ userId: '@foo:example.org',
+ roomId: '!somewhere:example.org'
+ }));
}
function test_createRoomDirectInviteMultiple() {
diff --git a/src/tests/quick-tests/tst_MatrixLink.qml b/src/tests/quick-tests/tst_MatrixLink.qml
new file mode 100644
--- /dev/null
+++ b/src/tests/quick-tests/tst_MatrixLink.qml
@@ -0,0 +1,161 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2025 nannanko <nannanko@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtTest
+import QtQml.Models
+
+import 'test-helpers' as QmlHelpers
+import '../../contents/ui/event-types' as Events
+
+QmlHelpers.TestItem {
+ id: item
+
+ property var mentionUserRequested: mockHelper.noop()
+ property var matrixSdk: QmlHelpers.MatrixSdkMock {}
+ property var switchToRoomRequested: mockHelper.noop()
+ property var pushCreateRoomPage: mockHelper.noop()
+ property var pageStack: ({
+ currentItem: {
+ presetPage: mockHelper.noop(),
+ delayGoToEvent: mockHelper.noop()
+ }
+ })
+ property var sdkVars: ({
+ roomList: {
+ exist: (roomId) => {
+ const rooms = ["!joinedroomid:example.org", "#joinedroomalias:example.org"];
+ return rooms.includes(roomId);
+ }
+ }
+ })
+ property var pushJoinRoomPage: mockHelper.noop()
+
+ Events.Text {
+ id: textEvent
+ }
+
+ TestCase {
+ id: matrixLinkTest
+ name: 'matrixLinkTest'
+ when: windowShown
+
+ function init() {
+ mockHelper.clearAll();
+ }
+
+ function test_matrixSchemeUser() {
+ textEvent.openLink("matrix:u/someone:example.org");
+ compare(mentionUserRequested.calledTimes(), 1);
+ compare(mentionUserRequested.lastArgs()[0], "@someone:example.org");
+ }
+
+ function test_matrixToUser() {
+ textEvent.openLink("https://matrix.to/#/@someone:example.org");
+ compare(mentionUserRequested.calledTimes(), 1);
+ compare(mentionUserRequested.lastArgs()[0], "@someone:example.org");
+ }
+
+ function test_matrixSchemeUserWithAction() {
+ textEvent.openLink("matrix:u/someonewithchat:example.org?action=chat");
+ compare(switchToRoomRequested.calledTimes(), 1);
+ compare(switchToRoomRequested.lastArgs()[0], "!directroom:example.org");
+ }
+
+ function test_matrixSchemeUserCreateDirectRoom() {
+ textEvent.openLink("matrix:u/someonewithoutchat:example.org?action=chat");
+ compare(pushCreateRoomPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.lastArgs()[0], "@someonewithoutchat:example.org");
+ }
+
+ function test_matrixSchemeRoomId() {
+ textEvent.openLink("matrix:roomid/joinedroomid:example.org");
+ compare(switchToRoomRequested.calledTimes(), 1);
+ compare(switchToRoomRequested.lastArgs()[0], "!joinedroomid:example.org");
+ }
+
+ function test_matrixToRoomId() {
+ textEvent.openLink("https://matrix.to/#/!joinedroomid:example.org");
+ compare(switchToRoomRequested.calledTimes(), 1);
+ compare(switchToRoomRequested.lastArgs()[0], "!joinedroomid:example.org");
+ }
+
+ function test_matrixSchemeRoomAlias() {
+ textEvent.openLink("matrix:r/joinedroomalias:example.org");
+ compare(switchToRoomRequested.calledTimes(), 1);
+ compare(switchToRoomRequested.lastArgs()[0], "#joinedroomalias:example.org");
+ }
+
+ function test_matrixToRoomAlias() {
+ textEvent.openLink("https://matrix.to/#/#joinedroomalias:example.org");
+ compare(switchToRoomRequested.calledTimes(), 1);
+ compare(switchToRoomRequested.lastArgs()[0], "#joinedroomalias:example.org");
+ }
+
+ function test_matrixSchemeRoomIdUnjoined() {
+ textEvent.openLink("matrix:roomid/unjoinedroomid:example.org?action=join&via=server1.ca&via=server2.ca");
+ compare(pushJoinRoomPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.lastArgs()[0], "!unjoinedroomid:example.org");
+ compare(pageStack.currentItem.presetPage.lastArgs()[1], ["server1.ca", "server2.ca"]);
+ }
+
+ function test_matrixToRoomIdUnjoined() {
+ textEvent.openLink("https://matrix.to/#/!unjoinedroomid:example.org?via=server1.ca&via=server2.ca");
+ compare(pageStack.currentItem.presetPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.lastArgs()[0], "!unjoinedroomid:example.org");
+ compare(pageStack.currentItem.presetPage.lastArgs()[1], ["server1.ca", "server2.ca"]);
+ }
+
+ function test_matrixSchemeRoomAliasUnjoined() {
+ textEvent.openLink("matrix:r/unjoinedroomalias:example.org?action=join&via=server1.ca&via=server2.ca");
+ compare(pushJoinRoomPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.lastArgs()[0], "#unjoinedroomalias:example.org");
+ compare(pageStack.currentItem.presetPage.lastArgs()[1], ["server1.ca", "server2.ca"]);
+ }
+
+ function test_matrixToRoomAliasUnjoined() {
+ textEvent.openLink("https://matrix.to/#/#unjoinedroomalias:example.org?action=join&via=server1.ca&via=server2.ca");
+ compare(pushJoinRoomPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.lastArgs()[0], "#unjoinedroomalias:example.org");
+ compare(pageStack.currentItem.presetPage.lastArgs()[1], ["server1.ca", "server2.ca"]);
+ }
+
+ function test_matrixSchemeEvent() {
+ textEvent.openLink("matrix:roomid/joinedroomid:example.org/e/eventid");
+ compare(switchToRoomRequested.calledTimes(), 1);
+ compare(switchToRoomRequested.lastArgs()[0], "!joinedroomid:example.org");
+ compare(pageStack.currentItem.delayGoToEvent.calledTimes(), 1);
+ compare(pageStack.currentItem.delayGoToEvent.lastArgs()[0], "$eventid");
+ }
+
+ function test_matrixToEvent() {
+ textEvent.openLink("https://matrix.to/#/!joinedroomid:example.org/$eventid");
+ compare(switchToRoomRequested.calledTimes(), 1);
+ compare(switchToRoomRequested.lastArgs()[0], "!joinedroomid:example.org");
+ compare(pageStack.currentItem.delayGoToEvent.calledTimes(), 1);
+ compare(pageStack.currentItem.delayGoToEvent.lastArgs()[0], "$eventid");
+ }
+
+ function test_matrixSchemeEventUnjoined() {
+ textEvent.openLink("matrix:roomid/unjoinedroomid:example.org/e/eventid?via=server1.ca&via=server2.ca");
+ compare(pushJoinRoomPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.lastArgs()[0], "!unjoinedroomid:example.org");
+ compare(pageStack.currentItem.presetPage.lastArgs()[1], ["server1.ca", "server2.ca"]);
+ }
+
+ function test_matrixToEventUnjoined() {
+ textEvent.openLink("https://matrix.to/#/!unjoinedroomid:example.org/$eventid?via=server1.ca&via=server2.ca");
+ compare(pushJoinRoomPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.calledTimes(), 1);
+ compare(pageStack.currentItem.presetPage.lastArgs()[0], "!unjoinedroomid:example.org");
+ compare(pageStack.currentItem.presetPage.lastArgs()[1], ["server1.ca", "server2.ca"]);
+ }
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Sep 13, 2:42 AM (11 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
451393
Default Alt Text
D229.1757756568.diff (19 KB)
Attached To
Mode
D229: Handle matrix uri
Attached
Detach File
Event Timeline
Log In to Comment