Page MenuHomePhorge

D229.1757756568.diff
No OneTemporary

Size
19 KB
Referenced Files
None
Subscribers
None

D229.1757756568.diff

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

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)

Event Timeline