Compare commits
59 Commits
work/redst
...
work/tobia
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
658551e64d | ||
|
|
adc23e22cd | ||
|
|
a673d97144 | ||
|
|
5a71dfcd7a | ||
|
|
5f6c248181 | ||
|
|
8e04a5ed2f | ||
|
|
f45582dc03 | ||
|
|
19cfe118ac | ||
|
|
79b8987d85 | ||
|
|
1d9708ffa4 | ||
|
|
c55f30be5b | ||
|
|
669a00a7e3 | ||
|
|
51fe090043 | ||
|
|
cf85d0fc0d | ||
|
|
c257ac504b | ||
|
|
9412f7e189 | ||
|
|
385fafa51c | ||
|
|
c7e409abe9 | ||
|
|
98816aedd4 | ||
|
|
38c66d1b7d | ||
|
|
0e01a43aab | ||
|
|
ddf67aef94 | ||
|
|
fa8294d4b9 | ||
|
|
8ad822fd0b | ||
|
|
69568c628f | ||
|
|
62a770b3e2 | ||
|
|
9b65ae1e66 | ||
|
|
5d5295d06d | ||
|
|
8f7be9993c | ||
|
|
821d7621e3 | ||
|
|
91b00a34b7 | ||
|
|
19e74b60a9 | ||
|
|
5ee5991c39 | ||
|
|
abffeec938 | ||
|
|
77ac811498 | ||
|
|
1ba6b840db | ||
|
|
bfb640487f | ||
|
|
4b3cc750a1 | ||
|
|
8bef9713fd | ||
|
|
889bf9cbc6 | ||
|
|
003ab4f278 | ||
|
|
a6b9759a31 | ||
|
|
5c5dcd555b | ||
|
|
8098fce461 | ||
|
|
227ea231d5 | ||
|
|
35ac1b7a55 | ||
|
|
86be1d82bc | ||
|
|
c6dd82d2ff | ||
|
|
2624ed1817 | ||
|
|
8706ee950e | ||
|
|
bde27dc826 | ||
|
|
25b4ee2efb | ||
|
|
e7f6adaa01 | ||
|
|
bb3e28bc43 | ||
|
|
026db80391 | ||
|
|
2c09a48c8d | ||
|
|
6e0931e31b | ||
|
|
1424b8ce42 | ||
|
|
f5f151681d |
@@ -1,2 +1,2 @@
|
||||
[General]
|
||||
disableUnqualifiedAccess = "i18nc,xi18nc,i18ncp"
|
||||
disableUnqualifiedAccess = "i18nc,xi18nc,i18ncp,i18n"
|
||||
|
||||
@@ -44,6 +44,22 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "opencv",
|
||||
"config-opts": [
|
||||
"-DBUILD_TESTS=OFF",
|
||||
"-DWITH_GTK=OFF",
|
||||
"-DBUILD_LIST=core,imgproc"
|
||||
],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/opencv/opencv"
|
||||
}
|
||||
],
|
||||
"builddir": true
|
||||
},
|
||||
{
|
||||
"name": "kquickimageeditor",
|
||||
"config-opts": [
|
||||
|
||||
@@ -15,7 +15,7 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "6.17")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
set(QT_MIN_VERSION "6.8")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
|
||||
@@ -43,13 +43,13 @@ void MessageContentModelTest::missingEvent()
|
||||
|
||||
QCOMPARE(model1.rowCount(), 1);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading"_s);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading…"_s);
|
||||
|
||||
auto model2 = EventMessageContentModel(room, u"$153456789:example.org"_s, true);
|
||||
|
||||
QCOMPARE(model2.rowCount(), 1);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply"_s);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply…"_s);
|
||||
|
||||
room->syncNewEvents(u"test-min-sync.json"_s);
|
||||
QCOMPARE(model1.rowCount(), 2);
|
||||
|
||||
@@ -109,113 +109,20 @@ void Server::start()
|
||||
m_server.route(u"/_matrix/client/v3/rooms/<arg>/invite"_s,
|
||||
QHttpServerRequest::Method::Post,
|
||||
[this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) {
|
||||
m_invitedUsers[roomId] += QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString();
|
||||
Changes changes;
|
||||
changes.invitations += Changes::InviteUser{
|
||||
.userId = QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString(),
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok);
|
||||
});
|
||||
|
||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) {
|
||||
QMap<QString, QJsonArray> stateEvents;
|
||||
QMap<QString, QJsonArray> roomAccountData;
|
||||
|
||||
for (const auto &roomData : m_roomsToCreate) {
|
||||
stateEvents[roomData.id] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomData.id},
|
||||
{u"sender"_s, roomData.members[0]},
|
||||
{u"state_key"_s, QString()},
|
||||
{u"type"_s, u"m.room.create"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
for (const auto &member : roomData.members) {
|
||||
stateEvents[roomData.id] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomData.id},
|
||||
{u"sender"_s, member},
|
||||
{u"state_key"_s, member},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
QJsonObject tags;
|
||||
for (const auto &tag : roomData.tags) {
|
||||
tags[tag] = QJsonObject();
|
||||
}
|
||||
roomAccountData[roomData.id] += QJsonObject{{u"type"_s, u"m.tag"_s}, {u"content"_s, QJsonObject{{u"tags"_s, tags}}}};
|
||||
}
|
||||
m_roomsToCreate.clear();
|
||||
for (const auto &roomId : m_invitedUsers.keys()) {
|
||||
const auto &values = m_invitedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_invitedUsers.clear();
|
||||
|
||||
for (const auto &roomId : m_bannedUsers.keys()) {
|
||||
const auto &values = m_bannedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_bannedUsers.clear();
|
||||
|
||||
for (const auto &roomId : m_joinedUsers.keys()) {
|
||||
const auto &values = m_joinedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_joinedUsers.clear();
|
||||
|
||||
QJsonObject rooms;
|
||||
auto keys = stateEvents.keys() + m_events.keys();
|
||||
for (const auto &roomId : QSet(keys.begin(), keys.end())) {
|
||||
rooms[roomId] = QJsonObject{
|
||||
{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}},
|
||||
{u"account_data"_s, QJsonObject{{u"events"_s, roomAccountData[roomId]}}},
|
||||
{u"timeline"_s, QJsonObject{{u"events"_s, m_events[roomId]}}},
|
||||
};
|
||||
}
|
||||
m_events.clear();
|
||||
|
||||
auto json = QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}};
|
||||
responder.write(QJsonDocument(json), QHttpServerResponder::StatusCode::Ok);
|
||||
});
|
||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, this, &Server::sync);
|
||||
|
||||
QSslConfiguration config;
|
||||
QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s);
|
||||
key.open(QFile::ReadOnly);
|
||||
void(key.open(QFile::ReadOnly));
|
||||
config.setPrivateKey(QSslKey(&key, QSsl::Rsa));
|
||||
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front());
|
||||
m_sslServer.setSslConfiguration(config);
|
||||
@@ -229,46 +136,239 @@ void Server::start()
|
||||
|
||||
QString Server::createRoom(const QString &matrixId)
|
||||
{
|
||||
auto roomId = generateRoomId();
|
||||
m_roomsToCreate += RoomData{
|
||||
.members = {matrixId},
|
||||
.id = roomId,
|
||||
const auto roomId = generateRoomId();
|
||||
Changes changes;
|
||||
changes.newRooms += Changes::NewRoom{
|
||||
.initialMembers = {matrixId},
|
||||
.roomId = {roomId},
|
||||
.tags = {},
|
||||
};
|
||||
m_state += changes;
|
||||
return roomId;
|
||||
}
|
||||
|
||||
void Server::inviteUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
m_invitedUsers[roomId] += matrixId;
|
||||
Changes changes;
|
||||
changes.invitations += Changes::InviteUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
}
|
||||
|
||||
void Server::banUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
m_bannedUsers[roomId] += matrixId;
|
||||
Changes changes;
|
||||
changes.bans += Changes::BanUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
}
|
||||
|
||||
void Server::joinUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
m_joinedUsers[roomId] += matrixId;
|
||||
Changes changes;
|
||||
changes.joins += Changes::JoinUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
}
|
||||
|
||||
QString Server::createServerNoticesRoom(const QString &matrixId)
|
||||
{
|
||||
auto roomId = createRoom(matrixId);
|
||||
m_roomsToCreate.last().tags = {u"m.server_notice"_s};
|
||||
const auto roomId = generateRoomId();
|
||||
Changes changes;
|
||||
changes.newRooms += Changes::NewRoom{
|
||||
.initialMembers = {matrixId},
|
||||
.roomId = {roomId},
|
||||
.tags = {u"m.server_notice"_s},
|
||||
};
|
||||
m_state += changes;
|
||||
return roomId;
|
||||
}
|
||||
|
||||
QString Server::sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content)
|
||||
{
|
||||
Changes changes;
|
||||
const auto eventId = generateEventId();
|
||||
m_events[roomId] += QJsonObject{
|
||||
{u"type"_s, eventType},
|
||||
{u"content"_s, content},
|
||||
{u"sender"_s, u"@foo:server.com"_s},
|
||||
{u"event_id"_s, eventId},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
changes.events += Changes::Event{
|
||||
.fullJson = QJsonObject{{u"type"_s, eventType},
|
||||
{u"content"_s, content},
|
||||
{u"sender"_s, u"@foo:server.com"_s},
|
||||
{u"event_id"_s, eventId},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId}},
|
||||
};
|
||||
m_state += changes;
|
||||
return eventId;
|
||||
}
|
||||
|
||||
void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &responder)
|
||||
{
|
||||
QJsonObject joinRooms;
|
||||
auto token = request.query().queryItemValue(u"since"_s).toInt();
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &newRoom : change.newRooms) {
|
||||
QJsonArray stateEvents;
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, newRoom.roomId},
|
||||
{u"sender"_s, newRoom.initialMembers[0]},
|
||||
{u"state_key"_s, QString()},
|
||||
{u"type"_s, u"m.room.create"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
for (const auto &member : newRoom.initialMembers) {
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, newRoom.roomId},
|
||||
{u"sender"_s, member},
|
||||
{u"state_key"_s, member},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
|
||||
auto room = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents}}}};
|
||||
|
||||
QJsonArray roomAccountData;
|
||||
QJsonObject tags;
|
||||
for (const auto &tag : newRoom.tags) {
|
||||
tags[tag] = QJsonObject();
|
||||
}
|
||||
if (!tags.empty()) {
|
||||
roomAccountData += QJsonObject{{u"type"_s, u"m.tag"_s}, {u"content"_s, QJsonObject{{u"tags"_s, tags}}}};
|
||||
}
|
||||
|
||||
if (roomAccountData.size() > 0) {
|
||||
room[u"account_data"] = QJsonObject{{u"events"_s, roomAccountData}};
|
||||
}
|
||||
|
||||
joinRooms[newRoom.roomId] = room;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &invitation : change.invitations) {
|
||||
// TODO: The invitation could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[invitation.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, invitation.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, invitation.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(invitation.roomId)) {
|
||||
auto room = joinRooms[invitation.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[invitation.roomId] = room;
|
||||
} else {
|
||||
joinRooms[invitation.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &ban : change.bans) {
|
||||
// TODO: The ban could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[ban.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, ban.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, ban.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(ban.roomId)) {
|
||||
auto room = joinRooms[ban.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[ban.roomId] = room;
|
||||
} else {
|
||||
joinRooms[ban.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &join : change.joins) {
|
||||
// TODO: The join could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[join.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, join.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, join.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(join.roomId)) {
|
||||
auto room = joinRooms[join.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[join.roomId] = room;
|
||||
} else {
|
||||
joinRooms[join.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &event : change.events) {
|
||||
// TODO the room might be in a different join state.
|
||||
auto timeline = joinRooms[event.fullJson[u"room_id"_s].toString()][u"timeline"_s][u"events"_s].toArray();
|
||||
timeline += event.fullJson;
|
||||
if (joinRooms.contains(event.fullJson[u"room_id"_s].toString())) {
|
||||
auto room = joinRooms[event.fullJson[u"room_id"_s].toString()].toObject();
|
||||
room[u"timeline"_s] = QJsonObject{{u"events"_s, timeline}};
|
||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = room;
|
||||
} else {
|
||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = QJsonObject{
|
||||
{u"timeline"_s, QJsonObject{{u"events"_s, timeline}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject syncData = {
|
||||
// {u"account_data"_s, QJsonObject {}},
|
||||
// {u"presence"_s, QJsonObject {}},
|
||||
{u"next_batch"_s, QString::number(m_state.size())},
|
||||
};
|
||||
|
||||
QJsonObject rooms;
|
||||
if (!joinRooms.isEmpty()) {
|
||||
rooms[u"join"_s] = joinRooms;
|
||||
}
|
||||
|
||||
if (!rooms.empty()) {
|
||||
syncData[u"rooms"_s] = rooms;
|
||||
}
|
||||
|
||||
qWarning() << syncData;
|
||||
responder.write(QJsonDocument(syncData), QHttpServerResponder::StatusCode::Ok);
|
||||
}
|
||||
|
||||
@@ -2,16 +2,51 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include <QHttpServer>
|
||||
#include <QJsonObject>
|
||||
#include <QSslServer>
|
||||
|
||||
struct Changes {
|
||||
struct NewRoom {
|
||||
QStringList initialMembers;
|
||||
QString roomId;
|
||||
QStringList tags;
|
||||
};
|
||||
QList<NewRoom> newRooms;
|
||||
|
||||
struct InviteUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<InviteUser> invitations;
|
||||
|
||||
struct BanUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<BanUser> bans;
|
||||
|
||||
struct JoinUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<JoinUser> joins;
|
||||
|
||||
struct Event {
|
||||
QJsonObject fullJson;
|
||||
};
|
||||
QList<Event> events;
|
||||
};
|
||||
|
||||
struct RoomData {
|
||||
QStringList members;
|
||||
QString id;
|
||||
QStringList tags;
|
||||
};
|
||||
|
||||
class Server
|
||||
class Server : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Server();
|
||||
|
||||
@@ -37,10 +72,7 @@ private:
|
||||
QHttpServer m_server;
|
||||
QSslServer m_sslServer;
|
||||
|
||||
QHash<QString, QList<QString>> m_invitedUsers;
|
||||
QHash<QString, QList<QString>> m_bannedUsers;
|
||||
QHash<QString, QList<QString>> m_joinedUsers;
|
||||
void sync(const QHttpServerRequest &request, QHttpServerResponder &responder);
|
||||
|
||||
QList<RoomData> m_roomsToCreate;
|
||||
QMap<QString, QJsonArray> m_events;
|
||||
QList<Changes> m_state;
|
||||
};
|
||||
|
||||
@@ -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 <QTest>
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
@@ -32,7 +33,7 @@ public:
|
||||
if (!syncFileName.isEmpty()) {
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
Q_UNUSED(testSyncFile.open(QIODevice::ReadOnly));
|
||||
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
|
||||
update(std::move(roomData));
|
||||
@@ -46,7 +47,7 @@ inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFile
|
||||
if (!eventFileName.isEmpty()) {
|
||||
QFile testEventFile;
|
||||
testEventFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + eventFileName);
|
||||
testEventFile.open(QIODevice::ReadOnly);
|
||||
Q_UNUSED(testEventFile.open(QIODevice::ReadOnly));
|
||||
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
|
||||
return Quotient::loadEvent<EventT>(testSyncJson);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ void TimelineMessageModelTest::pendingEvent()
|
||||
// different every time.
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + u"test-pending-sync.json"_s);
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
QVERIFY(testSyncFile.open(QIODevice::ReadOnly));
|
||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
auto root = testSyncJson.object();
|
||||
auto timeline = root["timeline"_L1].toObject();
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||
|
||||
qt_add_executable(timeline-memtest
|
||||
qt_add_executable(timeline_memtest
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(timeline-memtest PRIVATE neochatplugin Timelineplugin)
|
||||
target_link_libraries(timeline-memtest PUBLIC
|
||||
target_link_libraries(timeline_memtest PRIVATE neochatplugin Timelineplugin)
|
||||
target_link_libraries(timeline_memtest PUBLIC
|
||||
Qt::Core
|
||||
Qt::Quick
|
||||
Qt::Qml
|
||||
@@ -16,14 +16,13 @@ target_link_libraries(timeline-memtest PUBLIC
|
||||
Qt::QuickControls2
|
||||
Qt::Widgets
|
||||
KF6::I18nQml
|
||||
KF6::Kirigami
|
||||
QuotientQt6
|
||||
LibNeoChat
|
||||
Timeline
|
||||
)
|
||||
|
||||
ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline-memtest
|
||||
ecm_add_qml_module(timeline_memtest URI org.kde.neochat.timeline_memtest GENERATE_PLUGIN_SOURCE
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline_memtest
|
||||
QML_FILES
|
||||
Main.qml
|
||||
SOURCES
|
||||
|
||||
@@ -28,7 +28,7 @@ int main(int argc, char **argv)
|
||||
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
|
||||
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
|
||||
|
||||
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
|
||||
engine.loadFromModule("org.kde.neochat.timeline_memtest", "Main");
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,11 @@ public:
|
||||
if (!syncFileName.isEmpty()) {
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
auto ok = testSyncFile.open(QIODevice::ReadOnly);
|
||||
if (!ok) {
|
||||
qWarning() << "Failed to open" << testSyncFile.fileName() << testSyncFile.errorString();
|
||||
}
|
||||
|
||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
|
||||
auto timelineJson = testSyncJson["timeline"_L1].toObject();
|
||||
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);
|
||||
|
||||
@@ -20,7 +20,7 @@ index 10fe66daa..cd063113d 100644
|
||||
|
||||
-set(KF_MIN_VERSION "6.17")
|
||||
+set(KF_MIN_VERSION "6.12")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
set(QT_MIN_VERSION "6.8")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
--
|
||||
|
||||
760
po/ar/neochat.po
760
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
501
po/az/neochat.po
501
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
605
po/ca/neochat.po
605
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1285
po/cs/neochat.po
1285
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
642
po/da/neochat.po
642
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
528
po/de/neochat.po
528
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
516
po/el/neochat.po
516
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
524
po/eo/neochat.po
524
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
743
po/es/neochat.po
743
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
609
po/eu/neochat.po
609
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
740
po/fi/neochat.po
740
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1003
po/fr/neochat.po
1003
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
612
po/gl/neochat.po
612
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
734
po/he/neochat.po
734
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
525
po/hi/neochat.po
525
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
612
po/hu/neochat.po
612
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
612
po/ia/neochat.po
612
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
520
po/id/neochat.po
520
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
498
po/ie/neochat.po
498
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
617
po/it/neochat.po
617
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
540
po/ja/neochat.po
540
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
603
po/ka/neochat.po
603
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
615
po/ko/neochat.po
615
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
540
po/lt/neochat.po
540
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1018
po/lv/neochat.po
1018
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
603
po/nl/neochat.po
603
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
531
po/nn/neochat.po
531
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
500
po/pa/neochat.po
500
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
524
po/pl/neochat.po
524
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
520
po/pt/neochat.po
520
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
612
po/ru/neochat.po
612
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
525
po/sa/neochat.po
525
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
514
po/sk/neochat.po
514
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
605
po/sl/neochat.po
605
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
605
po/sv/neochat.po
605
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
524
po/ta/neochat.po
524
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
798
po/tr/neochat.po
798
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
603
po/uk/neochat.po
603
po/uk/neochat.po
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
@@ -342,9 +342,6 @@ if(TARGET KF6::DBusAddons AND NOT WIN32)
|
||||
endif()
|
||||
|
||||
if (TARGET KUnifiedPush)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
|
||||
target_link_libraries(neochat PUBLIC KUnifiedPush)
|
||||
|
||||
if (NOT ANDROID)
|
||||
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "enums/roomsortparameter.h"
|
||||
#include "general_logging.h"
|
||||
#include "mediasizehelper.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/messagemodel.h"
|
||||
@@ -37,14 +38,6 @@
|
||||
#include "trayicon_sni.h"
|
||||
#endif
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusMessage>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
#include <kunifiedpush/connector.h>
|
||||
#endif
|
||||
@@ -133,8 +126,10 @@ Controller::Controller(QObject *parent)
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
||||
#endif
|
||||
|
||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
#ifndef Q_OS_ANDROID
|
||||
delete m_trayIcon;
|
||||
#endif
|
||||
NeoChatConfig::self()->save();
|
||||
});
|
||||
|
||||
@@ -200,13 +195,15 @@ void Controller::setAccountManager(AccountManager *manager)
|
||||
|
||||
m_accountManager = manager;
|
||||
|
||||
if (m_accountManager) {
|
||||
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
||||
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
||||
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
||||
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
||||
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
||||
if (!m_accountManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
||||
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
||||
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
||||
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
||||
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
||||
}
|
||||
|
||||
void Controller::initConnection(NeoChatConnection *connection)
|
||||
@@ -262,8 +259,8 @@ bool Controller::supportSystemTray() const
|
||||
#ifdef Q_OS_ANDROID
|
||||
return false;
|
||||
#else
|
||||
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
|
||||
return de != u"GNOME"_s && de != u"Pantheon"_s;
|
||||
QStringList unsupportedPlatforms{u"GNOME"_s, u"Pantheon"_s};
|
||||
return !unsupportedPlatforms.contains(QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP")));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -273,11 +270,8 @@ void Controller::setQuitOnLastWindowClosed()
|
||||
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
} else {
|
||||
if (m_trayIcon) {
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
} else if (m_trayIcon) {
|
||||
delete m_trayIcon;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -334,30 +328,7 @@ void Controller::clearInvitationNotification(const QString &roomId)
|
||||
|
||||
void Controller::updateBadgeNotificationCount(int count)
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
// copied from Telegram desktop
|
||||
const auto launcherUrl = "application://org.kde.neochat.desktop"_L1;
|
||||
// Gnome requires that count is a 64bit integer
|
||||
const qint64 counterSlice = std::min(count, 9999);
|
||||
QVariantMap dbusUnityProperties;
|
||||
|
||||
if (counterSlice > 0) {
|
||||
dbusUnityProperties["count"_L1] = counterSlice;
|
||||
dbusUnityProperties["count-visible"_L1] = true;
|
||||
} else {
|
||||
dbusUnityProperties["count-visible"_L1] = false;
|
||||
}
|
||||
|
||||
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_L1, "com.canonical.Unity.LauncherEntry"_L1, "Update"_L1);
|
||||
|
||||
signal.setArguments({launcherUrl, dbusUnityProperties});
|
||||
|
||||
QDBusConnection::sessionBus().send(signal);
|
||||
#endif // Q_OS_ANDROID
|
||||
#else
|
||||
qGuiApp->setBadgeNumber(count);
|
||||
#endif // QT_VERSION_CHECK(6, 6, 0)
|
||||
}
|
||||
|
||||
bool Controller::isFlatpak() const
|
||||
@@ -378,7 +349,10 @@ QString Controller::loadFileContent(const QString &path) const
|
||||
{
|
||||
QUrl url(path);
|
||||
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||
file.open(QFile::ReadOnly);
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
qCWarning(GENERAL) << "Failed to open file" << path;
|
||||
return {};
|
||||
}
|
||||
return QString::fromLatin1(file.readAll());
|
||||
}
|
||||
|
||||
|
||||
@@ -110,8 +110,9 @@ private:
|
||||
void initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection);
|
||||
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
QPointer<TrayIcon> m_trayIcon;
|
||||
#endif
|
||||
QString m_endpoint;
|
||||
QStringList m_shownImages;
|
||||
|
||||
|
||||
@@ -60,9 +60,8 @@ void NotificationsManager::startNotificationJob(QPointer<NeoChatConnection> conn
|
||||
}
|
||||
|
||||
if (!m_connActiveJob.contains(connection->user()->id())) {
|
||||
auto job = connection->callApi<GetNotificationsJob>();
|
||||
m_connActiveJob.append(connection->user()->id());
|
||||
connect(job, &BaseJob::success, this, [this, job, connection]() {
|
||||
connection->callApi<GetNotificationsJob>().onResult([this, connection](const auto &job) {
|
||||
m_connActiveJob.removeAll(connection->user()->id());
|
||||
processNotificationJob(connection, job, !m_oldNotifications.contains(connection->user()->id()));
|
||||
});
|
||||
|
||||
@@ -20,8 +20,7 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.OverlayZStacking.layer: Kirigami.OverlayZStacking.ToolTip
|
||||
z: Kirigami.OverlayZStacking.z
|
||||
z: 99
|
||||
spacing: 0
|
||||
|
||||
opacity: (!root.text.startsWith("https://matrix.to/") && root.text.length > 0) ? 1 : 0
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
@@ -120,7 +120,7 @@ Components.AlbumMaximizeComponent {
|
||||
|
||||
onOpened: forceActiveFocus()
|
||||
|
||||
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom, root.currentAuthor)
|
||||
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom)
|
||||
|
||||
onSaveItem: {
|
||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
|
||||
|
||||
@@ -215,21 +215,8 @@ Kirigami.Page {
|
||||
});
|
||||
}
|
||||
|
||||
function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText, hoveredLink, isThread) {
|
||||
const contextMenu = messageDelegateContextMenu.createObject(root, {
|
||||
selectedText: selectedText,
|
||||
hoveredLink: hoveredLink,
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
messageComponentType: messageComponentType,
|
||||
plainText: plainText,
|
||||
htmlText: htmlText,
|
||||
});
|
||||
contextMenu.popup();
|
||||
}
|
||||
|
||||
function onShowFileMenu(eventId, author, messageComponentType, plainText, mimeType, progressInfo, isThread) {
|
||||
const contextMenu = fileDelegateContextMenu.createObject(root, {
|
||||
function onShowDelegateMenu(eventId: string, author, messageComponentType, plainText: string, richText: string, mimeType: string, progressInfo, isThread: bool, selectedText: string, hoveredLink: string) {
|
||||
const contextMenu = delegateContextMenu.createObject(root, {
|
||||
author: author,
|
||||
eventId: eventId,
|
||||
plainText: plainText,
|
||||
@@ -262,16 +249,8 @@ Kirigami.Page {
|
||||
}
|
||||
|
||||
Component {
|
||||
id: messageDelegateContextMenu
|
||||
MessageDelegateContextMenu {
|
||||
room: root.currentRoom
|
||||
connection: root.currentRoom.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDelegateContextMenu
|
||||
FileDelegateContextMenu {
|
||||
id: delegateContextMenu
|
||||
DelegateContextMenu {
|
||||
room: root.currentRoom
|
||||
connection: root.currentRoom.connection
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
@@ -44,7 +44,7 @@ QQC2.ComboBox {
|
||||
required property bool isHomeServer
|
||||
required property bool isDeletable
|
||||
|
||||
text: isAddServerDelegate ? i18n("Add New Server") : url
|
||||
text: isAddServerDelegate ? i18nc("@action:button", "Add New Server") : url
|
||||
highlighted: index === root.highlightedIndex
|
||||
|
||||
topInset: index === 0 ? Kirigami.Units.smallSpacing : Math.round(Kirigami.Units.smallSpacing / 2)
|
||||
@@ -60,7 +60,7 @@ QQC2.ComboBox {
|
||||
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: serverItem
|
||||
subtitle: serverItem.isHomeServer ? i18n("Home Server") : ""
|
||||
subtitle: serverItem.isHomeServer ? i18nc("@info", "Home Server") : ""
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
@@ -138,11 +138,11 @@ QQC2.ComboBox {
|
||||
text: {
|
||||
if (serverUrlField.length > 0) {
|
||||
if (!serverUrlField.acceptableInput) {
|
||||
return i18n("The entered text is not a valid url");
|
||||
return i18nc("@info", "The entered text is not a valid url");
|
||||
}
|
||||
|
||||
if (!serverUrlField.isValidServer) {
|
||||
return i18n("This server cannot be resolved or has already been added");
|
||||
return i18nc("@info", "This server cannot be resolved or has already been added");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ QQC2.ComboBox {
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: i18n("Server URL:")
|
||||
text: i18nc("@label", "Server URL:")
|
||||
}
|
||||
|
||||
QQC2.TextField {
|
||||
|
||||
@@ -45,6 +45,8 @@ Kirigami.Action {
|
||||
model: Purpose.PurposeAlternativesModel {
|
||||
pluginType: "Export"
|
||||
inputData: root.inputData
|
||||
// We already have many better ways to copy events to the clipboard
|
||||
disabledPlugins: ["clipboardplugin"]
|
||||
}
|
||||
|
||||
delegate: Kirigami.Action {
|
||||
|
||||
@@ -130,6 +130,6 @@ FormCard.FormCardPage {
|
||||
|
||||
property OpenFileDialog openFileDialog: OpenFileDialog {
|
||||
id: openFileDialog
|
||||
onChosen: securityKeyField.text = Controller.loadFileContent(path)
|
||||
onChosen: path => securityKeyField.text = Controller.loadFileContent(path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ Kirigami.Dialog {
|
||||
level: 1
|
||||
Layout.fillWidth: true
|
||||
font.bold: true
|
||||
clip: true // Intentional to limit insane Unicode in display names
|
||||
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
@@ -274,6 +275,20 @@ Kirigami.Dialog {
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button %1 is the name of the user.", "Search room for %1's messages", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName))
|
||||
icon.name: "search-symbolic"
|
||||
onClicked: {
|
||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
|
||||
room: root.room,
|
||||
senderId: root.user.id
|
||||
}, {
|
||||
title: i18nc("@action:title", "Search")
|
||||
});
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18n("Copy link")
|
||||
icon.name: "username-copy"
|
||||
|
||||
@@ -71,15 +71,15 @@ SearchPage {
|
||||
}
|
||||
QQC2.Label {
|
||||
visible: userDelegate.directChatExists
|
||||
text: i18n("Friends")
|
||||
text: i18nc("@info", "Friends")
|
||||
textFormat: Text.PlainText
|
||||
color: Kirigami.Theme.positiveTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
searchFieldPlaceholder: i18n("Find your friends…")
|
||||
noSearchPlaceholderMessage: i18n("Enter text to start searching for your friends")
|
||||
searchFieldPlaceholder: i18nc("@info:placeholder", "Find your friends…")
|
||||
noSearchPlaceholderMessage: i18nc("@info:placeholder", "Enter text to start searching for your friends")
|
||||
noResultPlaceholderMessage: i18nc("@info:label", "No matches found")
|
||||
|
||||
noSearchHelpfulAction: noResultHelpfulAction
|
||||
@@ -101,8 +101,8 @@ SearchPage {
|
||||
function openManualUserDialog() {
|
||||
let dialog = manualUserDialog.createObject(this, {
|
||||
connection: root.connection
|
||||
});
|
||||
dialog.parent = root.Window.window.overlay;
|
||||
}) as ManualUserDialog;
|
||||
dialog.parent = root.QQC2.Overlay.overlay;
|
||||
dialog.accepted.connect(() => {
|
||||
root.closeDialog();
|
||||
});
|
||||
|
||||
@@ -266,7 +266,7 @@ void RoomManager::viewEventSource(const QString &eventId)
|
||||
Q_EMIT showEventSource(eventId);
|
||||
}
|
||||
|
||||
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText, const QString &hoveredLink)
|
||||
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, const QString &selectedText, const QString &hoveredLink)
|
||||
{
|
||||
if (eventId.isEmpty()) {
|
||||
qWarning() << "Tried to open event menu with empty event id";
|
||||
@@ -279,23 +279,15 @@ void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, Neoch
|
||||
return;
|
||||
}
|
||||
const auto &event = **it;
|
||||
if (EventHandler::mediaInfo(room, &event).contains("mimeType"_L1)) {
|
||||
Q_EMIT showFileMenu(eventId,
|
||||
sender,
|
||||
Q_EMIT showDelegateMenu(eventId,
|
||||
room->qmlSafeMember(event.senderId()),
|
||||
MessageComponentType::typeForEvent(event),
|
||||
EventHandler::plainBody(room, &event),
|
||||
EventHandler::richBody(room, &event),
|
||||
EventHandler::mediaInfo(room, &event)["mimeType"_L1].toString(),
|
||||
room->fileTransferInfo(eventId));
|
||||
return;
|
||||
}
|
||||
|
||||
Q_EMIT showMessageMenu(eventId,
|
||||
sender,
|
||||
MessageComponentType::typeForEvent(event),
|
||||
EventHandler::plainBody(room, &event),
|
||||
EventHandler::richBody(room, &event),
|
||||
selectedText,
|
||||
hoveredLink);
|
||||
room->fileTransferInfo(eventId),
|
||||
selectedText,
|
||||
hoveredLink);
|
||||
}
|
||||
|
||||
bool RoomManager::hasOpenRoom() const
|
||||
|
||||
@@ -222,8 +222,7 @@ public:
|
||||
/**
|
||||
* @brief Show a context menu for the given event.
|
||||
*/
|
||||
Q_INVOKABLE void
|
||||
viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText = {}, const QString &hoveredLink = {});
|
||||
Q_INVOKABLE void viewEventMenu(const QString &eventId, NeoChatRoom *room, const QString &selectedText = {}, const QString &hoveredLink = {});
|
||||
|
||||
/**
|
||||
* @brief Set a URL to be loaded as the initial room.
|
||||
@@ -296,23 +295,15 @@ Q_SIGNALS:
|
||||
/**
|
||||
* @brief Request to show a menu for the given event.
|
||||
*/
|
||||
void showMessageMenu(const QString &eventId,
|
||||
const NeochatRoomMember *author,
|
||||
MessageComponentType::Type messageComponentType,
|
||||
const QString &plainText,
|
||||
const QString &htmlText,
|
||||
const QString &selectedText,
|
||||
const QString &hoveredLink);
|
||||
|
||||
/**
|
||||
* @brief Request to show a menu for the given media event.
|
||||
*/
|
||||
void showFileMenu(const QString &eventId,
|
||||
const NeochatRoomMember *author,
|
||||
MessageComponentType::Type messageComponentType,
|
||||
const QString &plainText,
|
||||
const QString &mimeType,
|
||||
const FileTransferInfo &progressInfo);
|
||||
void showDelegateMenu(const QString &eventId,
|
||||
const NeochatRoomMember *author,
|
||||
MessageComponentType::Type messageComponentType,
|
||||
const QString &plainText,
|
||||
const QString &richtText,
|
||||
const QString &mimeType,
|
||||
const FileTransferInfo &progressInfo,
|
||||
const QString &selectedText,
|
||||
const QString &hoveredLink);
|
||||
|
||||
/**
|
||||
* @brief Show the direct chat confirmation dialog.
|
||||
|
||||
@@ -253,7 +253,8 @@ QQC2.Control {
|
||||
placeholderText: root.currentRoom.usesEncryption ? i18nc("@placeholder", "Send an encrypted message…") : root.currentRoom.mainCache.attachmentPath.length > 0 ? i18nc("@placeholder", "Set an attachment caption…") : i18nc("@placeholder", "Send a message…")
|
||||
verticalAlignment: TextEdit.AlignVCenter
|
||||
wrapMode: TextEdit.Wrap
|
||||
textFormat: TextEdit.MarkdownText
|
||||
// This has to stay PlainText or else formatting starts breaking in strange ways
|
||||
textFormat: TextEdit.PlainText
|
||||
|
||||
Accessible.description: placeholderText
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ QQC2.Popup {
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: completionDelegate
|
||||
labelItem.textFormat: Text.PlainText
|
||||
labelItem.clip: true // Intentional to limit insane Unicode in display names
|
||||
subtitle: completionDelegate.subtitle ?? ""
|
||||
subtitleItem.textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ QQC2.ScrollView {
|
||||
readonly property int emojisPerRow: emojis.width / targetIconSize
|
||||
required property bool withCustom
|
||||
readonly property var searchCategory: withCustom ? EmojiModel.Search : EmojiModel.SearchNoCustom
|
||||
required property QtObject header
|
||||
required property Item header
|
||||
property bool stickers: false
|
||||
|
||||
signal chosen(string unicode)
|
||||
@@ -75,7 +75,7 @@ QQC2.ScrollView {
|
||||
shortName: modelData.shortName,
|
||||
unicode: modelData.unicode,
|
||||
categoryIconSize: root.targetIconSize
|
||||
});
|
||||
}) as EmojiTonesPicker;
|
||||
tones.open();
|
||||
tones.forceActiveFocus();
|
||||
}
|
||||
@@ -85,14 +85,14 @@ QQC2.ScrollView {
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
icon.name: root.stickers ? "stickers" : "preferences-desktop-emoticons"
|
||||
text: root.stickers ? i18n("No stickers") : i18n("No emojis")
|
||||
text: root.stickers ? i18nc("@info", "No stickers") : i18nc("@info", "No emojis")
|
||||
visible: emojis.count === 0
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: tonesPopupComponent
|
||||
EmojiTonesPicker {
|
||||
onChosen: root.chosen(emoji)
|
||||
onChosen: emoji => root.chosen(emoji)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
@@ -51,14 +53,14 @@ ColumnLayout {
|
||||
Kirigami.Action {
|
||||
id: emojis
|
||||
icon.name: "smiley"
|
||||
text: i18n("Emojis")
|
||||
text: i18nc("@action:button", "Emojis")
|
||||
checked: true
|
||||
onTriggered: root.selectedType = 0
|
||||
},
|
||||
Kirigami.Action {
|
||||
id: stickers
|
||||
icon.name: "stickers"
|
||||
text: i18n("Stickers")
|
||||
text: i18nc("@action:button", "Stickers")
|
||||
onTriggered: root.selectedType = 1
|
||||
}
|
||||
]
|
||||
@@ -107,7 +109,7 @@ ColumnLayout {
|
||||
id: searchField
|
||||
Layout.margins: Kirigami.Units.smallSpacing
|
||||
Layout.fillWidth: true
|
||||
visible: selectedType === 0
|
||||
visible: root.selectedType === 0
|
||||
|
||||
/**
|
||||
* The focus is manged by the parent and we don't want to use the standard
|
||||
@@ -127,17 +129,17 @@ ColumnLayout {
|
||||
header: categories
|
||||
Keys.forwardTo: searchField
|
||||
stickers: root.selectedType === 1
|
||||
onStickerChosen: stickerModel.postSticker(emoticonFilterModel.mapToSource(emoticonFilterModel.index(index, 0)).row)
|
||||
onStickerChosen: index => stickerModel.postSticker(emoticonFilterModel.mapToSource(emoticonFilterModel.index(index, 0)).row)
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
visible: showQuickReaction
|
||||
visible: root.showQuickReaction
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
visible: showQuickReaction
|
||||
visible: root.showQuickReaction
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height
|
||||
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
|
||||
@@ -149,6 +151,7 @@ ColumnLayout {
|
||||
model: ["👍", "👎", "😄", "🎉", "😕", "❤", "🚀", "👀"]
|
||||
|
||||
delegate: EmojiDelegate {
|
||||
required property string modelData
|
||||
emoji: modelData
|
||||
|
||||
height: root.categoryIconSize
|
||||
@@ -184,11 +187,14 @@ ColumnLayout {
|
||||
Component {
|
||||
id: emojiDelegate
|
||||
Kirigami.NavigationTabButton {
|
||||
required property string emoji
|
||||
required property int index
|
||||
required property string name
|
||||
width: root.categoryIconSize
|
||||
height: width
|
||||
checked: categories.currentIndex === model.index
|
||||
text: modelData ? modelData.emoji : ""
|
||||
QQC2.ToolTip.text: modelData ? modelData.name : ""
|
||||
checked: categories.currentIndex === index
|
||||
text: emoji
|
||||
QQC2.ToolTip.text: name
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
onClicked: {
|
||||
@@ -201,21 +207,25 @@ ColumnLayout {
|
||||
Component {
|
||||
id: stickerDelegate
|
||||
Kirigami.NavigationTabButton {
|
||||
id: sticker
|
||||
required property string name
|
||||
required property int index
|
||||
required property string emoji
|
||||
width: root.categoryIconSize
|
||||
height: width
|
||||
checked: stickerModel.packIndex === model.index
|
||||
checked: stickerModel.packIndex === index
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
contentItem: Image {
|
||||
source: model.url
|
||||
source: sticker.emoji
|
||||
fillMode: Image.PreserveAspectFit
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
QQC2.ToolTip.text: model.name
|
||||
QQC2.ToolTip.text: name
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered && !!model.name
|
||||
onClicked: stickerModel.packIndex = model.index
|
||||
QQC2.ToolTip.visible: hovered && !!name
|
||||
onClicked: stickerModel.packIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ void StateModel::loadState()
|
||||
beginResetModel();
|
||||
m_stateEvents.clear();
|
||||
if (!m_room) {
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
const auto keys = m_room->currentState().events().keys();
|
||||
|
||||
@@ -66,6 +66,13 @@ ecm_add_qml_module(LibNeoChat GENERATE_PLUGIN_SOURCE
|
||||
io.github.quotient_im.libquotient
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(LibNeoChat
|
||||
HEADER "general_logging.h"
|
||||
IDENTIFIER "GENERAL"
|
||||
CATEGORY_NAME "org.kde.neochat"
|
||||
DEFAULT_SEVERITY Info
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(LibNeoChat
|
||||
HEADER "eventhandler_logging.h"
|
||||
IDENTIFIER "EventHandling"
|
||||
@@ -105,3 +112,8 @@ if(NOT ANDROID)
|
||||
)
|
||||
target_compile_definitions(LibNeoChat PUBLIC -DHAVE_ICU)
|
||||
endif()
|
||||
|
||||
if (TARGET KUnifiedPush)
|
||||
target_compile_definitions(LibNeoChat PUBLIC -DHAVE_KUNIFIEDPUSH)
|
||||
target_link_libraries(LibNeoChat PUBLIC KUnifiedPush)
|
||||
endif()
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include "general_logging.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
AccountManager::AccountManager(bool testMode, QObject *parent)
|
||||
@@ -23,10 +25,10 @@ AccountManager::AccountManager(bool testMode, QObject *parent)
|
||||
loadAccountsFromCache();
|
||||
});
|
||||
} else {
|
||||
auto c = new NeoChatConnection(QUrl(u"https://localhost:1234"_s), this);
|
||||
c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
|
||||
m_accountRegistry->add(c);
|
||||
c->syncLoop();
|
||||
auto connection = new NeoChatConnection(QUrl(u"https://localhost:1234"_s), this);
|
||||
connection->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
|
||||
m_accountRegistry->add(connection);
|
||||
connection->syncLoop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,48 +39,45 @@ Quotient::AccountRegistry *AccountManager::accounts()
|
||||
|
||||
void AccountManager::loadAccountsFromCache()
|
||||
{
|
||||
const auto accounts = Quotient::SettingsGroup("Accounts"_L1).childGroups();
|
||||
for (const auto &accountId : accounts) {
|
||||
for (const auto &accountId : Quotient::SettingsGroup("Accounts"_L1).childGroups()) {
|
||||
Quotient::AccountSettings account{accountId};
|
||||
m_accountsLoading += accountId;
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
if (!account.homeserver().isEmpty()) {
|
||||
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
|
||||
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
|
||||
Quotient::AccountSettings account{accountId};
|
||||
QString accessToken;
|
||||
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
|
||||
accessToken = QString::fromLatin1(accessTokenLoadingJob->binaryData());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
auto connection = new NeoChatConnection(account.homeserver());
|
||||
m_connectionsLoading[accountId] = connection;
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
|
||||
connection->loadState();
|
||||
if (connection->allRooms().size() == 0 || connection->allRooms()[0]->currentState().get<Quotient::RoomCreateEvent>()) {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
} else {
|
||||
connect(
|
||||
connection->allRooms()[0],
|
||||
&NeoChatRoom::baseStateLoaded,
|
||||
this,
|
||||
[this, connection, accountId]() {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
});
|
||||
connection->assumeIdentity(account.userId(), account.deviceId(), accessToken);
|
||||
});
|
||||
if (account.homeserver().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
|
||||
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
|
||||
if (accessTokenLoadingJob->error() != QKeychain::Error::NoError) {
|
||||
return;
|
||||
}
|
||||
|
||||
Quotient::AccountSettings account{accountId};
|
||||
auto connection = new NeoChatConnection(account.homeserver());
|
||||
m_connectionsLoading[accountId] = connection;
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
|
||||
connection->loadState();
|
||||
if (connection->allRooms().size() == 0 || connection->allRooms()[0]->currentState().get<Quotient::RoomCreateEvent>()) {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
} else {
|
||||
connect(
|
||||
connection->allRooms()[0],
|
||||
&NeoChatRoom::baseStateLoaded,
|
||||
this,
|
||||
[this, connection, accountId]() {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
});
|
||||
connection->assumeIdentity(account.userId(), account.deviceId(), QString::fromLatin1(accessTokenLoadingJob->binaryData()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +93,7 @@ void AccountManager::saveAccessTokenToKeyChain(NeoChatConnection *connection)
|
||||
}
|
||||
const auto userId = connection->userId();
|
||||
|
||||
qDebug() << "Save the access token to the keychain for " << userId;
|
||||
qCDebug(GENERAL) << "Save the access token to the keychain for " << userId;
|
||||
auto job = new QKeychain::WritePasswordJob(qAppName());
|
||||
job->setAutoDelete(true);
|
||||
job->setKey(userId);
|
||||
@@ -109,7 +108,7 @@ void AccountManager::saveAccessTokenToKeyChain(NeoChatConnection *connection)
|
||||
|
||||
QKeychain::ReadPasswordJob *AccountManager::loadAccessTokenFromKeyChain(const QString &userId)
|
||||
{
|
||||
qDebug() << "Reading access token from the keychain for" << userId;
|
||||
qCDebug(GENERAL) << "Reading access token from the keychain for" << userId;
|
||||
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
|
||||
job->setKey(userId);
|
||||
|
||||
@@ -205,8 +204,7 @@ void AccountManager::dropConnection(const QString &userId)
|
||||
if (dropConnectionLoading(m_connectionsLoading.value(userId, nullptr))) {
|
||||
return;
|
||||
}
|
||||
const auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry->get(userId));
|
||||
if (connection) {
|
||||
if (const auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry->get(userId))) {
|
||||
dropRegistry(connection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ QString ChatDocumentHandler::getText() const
|
||||
qCWarning(ChatDocumentHandling) << "getText called with no QQuickTextDocument available.";
|
||||
return {};
|
||||
}
|
||||
return document()->toRawText();
|
||||
return document()->toPlainText();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::pushMention(const Mention mention) const
|
||||
|
||||
@@ -116,12 +116,14 @@ Q_SIGNALS:
|
||||
void textItemChanged();
|
||||
void roomChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void updateCompletion() const;
|
||||
|
||||
private:
|
||||
ChatBarType::Type m_type = ChatBarType::None;
|
||||
QPointer<QQuickItem> m_textItem;
|
||||
QTextDocument *document() const;
|
||||
|
||||
void updateCompletion() const;
|
||||
int completionStartIndex() const;
|
||||
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
|
||||
@@ -186,6 +186,10 @@ int DelegateSizeHelper::availablePercentageWidth() const
|
||||
qreal DelegateSizeHelper::availableWidth() const
|
||||
{
|
||||
qreal absoluteWidth = maxAvailableWidth() * availablePercentageWidth() * 0.01;
|
||||
// We want to use all available space for a horizontal line.
|
||||
if (m_startPercentWidth == m_endPercentWidth) {
|
||||
return std::round(absoluteWidth);
|
||||
}
|
||||
return std::round(std::min(absoluteWidth, maxWidth()));
|
||||
}
|
||||
|
||||
|
||||
@@ -61,14 +61,7 @@ void LinkPreviewer::loadUrlPreview()
|
||||
return;
|
||||
}
|
||||
|
||||
BaseJob *job = nullptr;
|
||||
if (conn->supportedMatrixSpecVersions().contains("v1.11"_L1)) {
|
||||
job = conn->callApi<GetUrlPreviewAuthedJob>(m_url);
|
||||
} else {
|
||||
QT_IGNORE_DEPRECATIONS(job = conn->callApi<GetUrlPreviewJob>(m_url);)
|
||||
}
|
||||
|
||||
connect(job, &BaseJob::success, this, [this, job, conn]() {
|
||||
auto onSuccess = [this, conn](const auto &job) {
|
||||
const auto json = job->jsonData();
|
||||
m_title = json["og:title"_L1].toString().trimmed();
|
||||
m_description = json["og:description"_L1].toString().trimmed().replace("\n"_L1, " "_L1);
|
||||
@@ -85,7 +78,13 @@ void LinkPreviewer::loadUrlPreview()
|
||||
Q_EMIT descriptionChanged();
|
||||
Q_EMIT imageSourceChanged();
|
||||
Q_EMIT loadedChanged();
|
||||
});
|
||||
};
|
||||
|
||||
if (conn->supportedMatrixSpecVersions().contains("v1.11"_L1)) {
|
||||
conn->callApi<GetUrlPreviewAuthedJob>(m_url);
|
||||
} else {
|
||||
QT_IGNORE_DEPRECATIONS(conn->callApi<GetUrlPreviewJob>(m_url).onResult(onSuccess);)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,9 +66,7 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
{
|
||||
using namespace Quotient;
|
||||
|
||||
auto job = m_connection->uploadFile(location.toLocalFile());
|
||||
|
||||
connect(job, &BaseJob::success, this, [name, location, job, this] {
|
||||
m_connection->uploadFile(location.toLocalFile()).onResult([name, location, this](const auto &job) {
|
||||
const auto &data = m_connection->accountData("im.ponies.user_emotes"_L1);
|
||||
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
||||
auto emojiData = json["images"_L1].toObject();
|
||||
|
||||
@@ -44,8 +44,8 @@ QVariant ImagePacksModel::data(const QModelIndex &index, int role) const
|
||||
QHash<int, QByteArray> ImagePacksModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{DisplayNameRole, "displayName"},
|
||||
{AvatarUrlRole, "avatarUrl"},
|
||||
{DisplayNameRole, "name"},
|
||||
{AvatarUrlRole, "emoji"},
|
||||
{AttributionRole, "attribution"},
|
||||
{IdRole, "id"},
|
||||
};
|
||||
|
||||
@@ -12,33 +12,27 @@ LocationsModel::LocationsModel(QObject *parent)
|
||||
{
|
||||
connect(this, &LocationsModel::roomChanged, this, [this]() {
|
||||
for (const auto &event : m_room->messageEvents()) {
|
||||
if (!is<RoomMessageEvent>(*event)) {
|
||||
continue;
|
||||
}
|
||||
if (event->contentJson()["msgtype"_L1] == "m.location"_L1) {
|
||||
const auto &e = *event;
|
||||
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||
if (const auto &roomMessageEvent = event.viewAs<RoomMessageEvent>()) {
|
||||
if (roomMessageEvent->msgtype() == RoomMessageEvent::MsgType::Location) {
|
||||
addLocation(roomMessageEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
connect(m_room, &NeoChatRoom::aboutToAddHistoricalMessages, this, [this](const auto &events) {
|
||||
for (const auto &event : events) {
|
||||
if (!is<RoomMessageEvent>(*event)) {
|
||||
continue;
|
||||
}
|
||||
if (event->contentJson()["msgtype"_L1] == "m.location"_L1) {
|
||||
const auto &e = *event;
|
||||
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||
if (const auto &roomMessageEvent = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (roomMessageEvent->msgtype() == RoomMessageEvent::MsgType::Location) {
|
||||
addLocation(roomMessageEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::aboutToAddNewMessages, this, [this](const auto &events) {
|
||||
for (const auto &event : events) {
|
||||
if (!is<RoomMessageEvent>(*event)) {
|
||||
continue;
|
||||
}
|
||||
if (event->contentJson()["msgtype"_L1] == "m.location"_L1) {
|
||||
const auto &e = *event;
|
||||
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||
if (const auto &roomMessageEvent = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (roomMessageEvent->msgtype() == RoomMessageEvent::MsgType::Location) {
|
||||
addLocation(roomMessageEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -133,8 +133,7 @@ void NeoChatConnection::connectSignals()
|
||||
&Connection::connected,
|
||||
this,
|
||||
[this] {
|
||||
auto job = callApi<GetVersionsJob>(BackgroundRequest);
|
||||
connect(job, &GetVersionsJob::success, this, [this, job] {
|
||||
callApi<GetVersionsJob>(BackgroundRequest).onResult([this](const auto &job) {
|
||||
m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_L1);
|
||||
Q_EMIT canCheckMutualRoomsChanged();
|
||||
m_canEraseData = job->unstableFeatures().contains("org.matrix.msc4025"_L1) || job->versions().count("v1.10"_L1);
|
||||
@@ -237,24 +236,22 @@ bool NeoChatConnection::canCheckMutualRooms() const
|
||||
|
||||
void NeoChatConnection::changePassword(const QString ¤tPassword, const QString &newPassword)
|
||||
{
|
||||
auto job = callApi<ChangePasswordJob>(newPassword, false);
|
||||
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
AuthenticationData authData;
|
||||
authData.session = replyData["session"_L1].toString();
|
||||
authData.type = "m.login.password"_L1;
|
||||
authData.authInfo["password"_L1] = currentPassword;
|
||||
authData.authInfo["user"_L1] = user()->id();
|
||||
authData.authInfo["identifier"_L1] = QJsonObject{{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
|
||||
auto innerJob = callApi<ChangePasswordJob>(newPassword, false, authData);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Success);
|
||||
});
|
||||
connect(innerJob, &BaseJob::failure, this, [innerJob, this]() {
|
||||
Q_EMIT passwordStatus(innerJob->jsonData()["errcode"_L1] == "M_FORBIDDEN"_L1 ? PasswordStatus::Wrong : PasswordStatus::Other);
|
||||
});
|
||||
}
|
||||
callApi<ChangePasswordJob>(newPassword, false).onFailure([this, currentPassword, newPassword](const auto &job) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
AuthenticationData authData;
|
||||
authData.session = replyData["session"_L1].toString();
|
||||
authData.type = "m.login.password"_L1;
|
||||
authData.authInfo["password"_L1] = currentPassword;
|
||||
authData.authInfo["user"_L1] = user()->id();
|
||||
authData.authInfo["identifier"_L1] = QJsonObject{{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
|
||||
auto innerJob = callApi<ChangePasswordJob>(newPassword, false, authData)
|
||||
.then(
|
||||
[this]() {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Success);
|
||||
},
|
||||
[this](const auto &job) {
|
||||
Q_EMIT passwordStatus(job->jsonData()["errcode"_L1] == "M_FORBIDDEN"_L1 ? PasswordStatus::Wrong : PasswordStatus::Other);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -274,22 +271,18 @@ QString NeoChatConnection::label() const
|
||||
|
||||
void NeoChatConnection::deactivateAccount(const QString &password, const bool erase)
|
||||
{
|
||||
auto job = callApi<DeactivateAccountJob>();
|
||||
connect(job, &BaseJob::result, this, [this, job, password, erase] {
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
AuthenticationData authData;
|
||||
authData.session = replyData["session"_L1].toString();
|
||||
authData.authInfo["password"_L1] = password;
|
||||
authData.type = "m.login.password"_L1;
|
||||
authData.authInfo["user"_L1] = user()->id();
|
||||
QJsonObject identifier = {{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
|
||||
authData.authInfo["identifier"_L1] = identifier;
|
||||
auto innerJob = callApi<DeactivateAccountJob>(authData, QString{}, erase);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
logout(false);
|
||||
});
|
||||
}
|
||||
callApi<DeactivateAccountJob>().onFailure([password, erase, this](const auto &job) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
AuthenticationData authData;
|
||||
authData.session = replyData["session"_L1].toString();
|
||||
authData.authInfo["password"_L1] = password;
|
||||
authData.type = "m.login.password"_L1;
|
||||
authData.authInfo["user"_L1] = user()->id();
|
||||
QJsonObject identifier = {{"type"_L1, "m.id.user"_L1}, {"user"_L1, user()->id()}};
|
||||
authData.authInfo["identifier"_L1] = identifier;
|
||||
callApi<DeactivateAccountJob>(authData, QString{}, erase).onResult([this]() {
|
||||
logout(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -342,19 +335,19 @@ void NeoChatConnection::createRoom(const QString &name, const QString &topic, co
|
||||
});
|
||||
}
|
||||
|
||||
const auto job = Connection::createRoom(Connection::PublishRoom, QString(), name, topic, QStringList(), {}, {}, {}, initialStateEvents);
|
||||
if (!parent.isEmpty()) {
|
||||
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
|
||||
if (setChildParent) {
|
||||
Connection::createRoom(Connection::PublishRoom, QString(), name, topic, QStringList(), {}, {}, {}, initialStateEvents)
|
||||
.then(
|
||||
[parent, setChildParent, this](const auto &job) {
|
||||
if (parent.isEmpty() || !setChildParent) {
|
||||
return;
|
||||
}
|
||||
if (auto parentRoom = room(parent)) {
|
||||
parentRoom->setState(u"m.space.child"_s, job->roomId(), QJsonObject{{"via"_L1, QJsonArray{domain()}}});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT errorOccured(i18n("Room creation failed: %1", job->errorString()));
|
||||
});
|
||||
},
|
||||
[this](const auto &job) {
|
||||
Q_EMIT errorOccured(i18n("Room creation failed: %1", job->errorString()));
|
||||
});
|
||||
}
|
||||
|
||||
void NeoChatConnection::createSpace(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
|
||||
@@ -371,20 +364,19 @@ void NeoChatConnection::createSpace(const QString &name, const QString &topic, c
|
||||
});
|
||||
}
|
||||
|
||||
const auto job =
|
||||
Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, initialStateEvents, {}, QJsonObject{{"type"_L1, "m.space"_L1}});
|
||||
if (!parent.isEmpty()) {
|
||||
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
|
||||
if (setChildParent) {
|
||||
Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, initialStateEvents, {}, QJsonObject{{"type"_L1, "m.space"_L1}})
|
||||
.then(
|
||||
[parent, setChildParent, this](const auto &job) {
|
||||
if (parent.isEmpty() || !setChildParent) {
|
||||
return;
|
||||
}
|
||||
if (auto parentRoom = room(parent)) {
|
||||
parentRoom->setState(u"m.space.child"_s, job->roomId(), QJsonObject{{"via"_L1, QJsonArray{domain()}}});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT errorOccured(i18n("Space creation failed: %1", job->errorString()));
|
||||
});
|
||||
},
|
||||
[this](const auto &job) {
|
||||
Q_EMIT errorOccured(i18n("Space creation failed: %1", job->errorString()));
|
||||
});
|
||||
}
|
||||
|
||||
Quotient::ForgetRoomJob *NeoChatConnection::forgetRoom(const QString &id)
|
||||
@@ -533,7 +525,11 @@ KeyImport::Error NeoChatConnection::exportMegolmSessions(const QString &passphra
|
||||
}
|
||||
QUrl url(path);
|
||||
QFile file(url.toLocalFile());
|
||||
file.open(QFile::WriteOnly);
|
||||
auto ok = file.open(QFile::WriteOnly);
|
||||
if (!ok) {
|
||||
qWarning() << "Failed to open" << file.fileName() << file.errorString();
|
||||
return KeyImport::OtherError;
|
||||
}
|
||||
file.write(result.value());
|
||||
file.close();
|
||||
return KeyImport::Success;
|
||||
|
||||
@@ -261,7 +261,10 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body, std::optiona
|
||||
|
||||
QTemporaryFile file;
|
||||
file.setFileTemplate(QStringLiteral("XXXXXX.jpg"));
|
||||
file.open();
|
||||
auto ok = file.open();
|
||||
if (!ok) {
|
||||
qWarning() << "Failed to open" << file.fileName() << file.errorString();
|
||||
}
|
||||
|
||||
const auto thumbnailImage = sink.videoFrame().toImage();
|
||||
Q_UNUSED(thumbnailImage.save(file.fileName()))
|
||||
@@ -510,12 +513,9 @@ QUrl NeoChatRoom::avatarMediaUrl() const
|
||||
|
||||
void NeoChatRoom::changeAvatar(const QUrl &localFile)
|
||||
{
|
||||
const auto job = connection()->uploadFile(localFile.toLocalFile());
|
||||
if (isJobPending(job)) {
|
||||
connect(job, &BaseJob::success, this, [this, job] {
|
||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar"_L1, QString(), QJsonObject{{"url"_L1, job->contentUri().toString()}});
|
||||
});
|
||||
}
|
||||
connection()->uploadFile(localFile.toLocalFile()).onResult([this](const auto &job) {
|
||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar"_L1, QString(), QJsonObject{{"url"_L1, job->contentUri().toString()}});
|
||||
});
|
||||
}
|
||||
|
||||
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
|
||||
@@ -625,8 +625,8 @@ void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reaso
|
||||
|
||||
QString NeoChatRoom::historyVisibility() const
|
||||
{
|
||||
if (auto stateEvent = currentState().get("m.room.history_visibility"_L1)) {
|
||||
return stateEvent->contentJson()["history_visibility"_L1].toString();
|
||||
if (const auto stateEvent = currentState().get("m.room.history_visibility"_L1)) {
|
||||
return stateEvent->contentPart<QString>("history_visibility"_L1);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -883,10 +883,11 @@ void NeoChatRoom::addParent(const QString &parentId, bool canonical, bool setPar
|
||||
|
||||
setState("m.space.parent"_L1, parentId, QJsonObject{{"canonical"_L1, canonical}, {"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
|
||||
if (setParentChild) {
|
||||
if (auto parent = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
|
||||
parent->setState("m.space.child"_L1, id(), QJsonObject{{"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
}
|
||||
if (!setParentChild) {
|
||||
return;
|
||||
}
|
||||
if (auto parent = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
|
||||
parent->setState("m.space.child"_L1, id(), QJsonObject{{"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,23 +938,27 @@ void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool can
|
||||
}
|
||||
setState("m.space.child"_L1, childId, QJsonObject{{"via"_L1, QJsonArray{connection()->domain()}}, {"suggested"_L1, suggested}, {"order"_L1, order}});
|
||||
|
||||
if (setChildParent) {
|
||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||
if (child->canSendState("m.space.parent"_L1)) {
|
||||
child->setState("m.space.parent"_L1, id(), QJsonObject{{"canonical"_L1, canonical}, {"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
if (!setChildParent) {
|
||||
return;
|
||||
}
|
||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||
if (!child->canSendState("m.space.parent"_L1)) {
|
||||
return;
|
||||
}
|
||||
child->setState("m.space.parent"_L1, id(), QJsonObject{{"canonical"_L1, canonical}, {"via"_L1, QJsonArray{connection()->domain()}}});
|
||||
|
||||
if (canonical) {
|
||||
// Only one canonical parent can exist so make sure others are set to false.
|
||||
auto parentEvents = child->currentState().eventsOfType("m.space.parent"_L1);
|
||||
for (const auto &parentEvent : parentEvents) {
|
||||
if (parentEvent->contentPart<bool>("canonical"_L1)) {
|
||||
auto content = parentEvent->contentJson();
|
||||
content.insert("canonical"_L1, false);
|
||||
setState("m.space.parent"_L1, parentEvent->stateKey(), content);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!canonical) {
|
||||
return;
|
||||
}
|
||||
// Only one canonical parent can exist so make sure others are set to false.
|
||||
auto parentEvents = child->currentState().eventsOfType("m.space.parent"_L1);
|
||||
for (const auto &parentEvent : parentEvents) {
|
||||
if (!parentEvent->contentPart<bool>("canonical"_L1)) {
|
||||
continue;
|
||||
}
|
||||
auto content = parentEvent->contentJson();
|
||||
content.insert("canonical"_L1, false);
|
||||
setState("m.space.parent"_L1, parentEvent->stateKey(), content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -968,12 +973,14 @@ void NeoChatRoom::removeChild(const QString &childId, bool unsetChildParent)
|
||||
}
|
||||
setState("m.space.child"_L1, childId, {});
|
||||
|
||||
if (unsetChildParent) {
|
||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||
if (child->canSendState("m.space.parent"_L1) && child->currentState().contains("m.space.parent"_L1, id())) {
|
||||
child->setState("m.space.parent"_L1, id(), {});
|
||||
}
|
||||
if (!unsetChildParent) {
|
||||
return;
|
||||
}
|
||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||
if (!child->canSendState("m.space.parent"_L1) || !child->currentState().contains("m.space.parent"_L1, id())) {
|
||||
return;
|
||||
}
|
||||
child->setState("m.space.parent"_L1, id(), {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1105,10 +1112,8 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions = {pushCondition};
|
||||
|
||||
// Add new override rule and make sure it's enabled
|
||||
auto job = connection()->callApi<SetPushRuleJob>("override"_L1, id(), actions, QString(), QString(), conditions, QString());
|
||||
connect(job, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("override"_L1, id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
connection()->callApi<SetPushRuleJob>("override"_L1, id(), actions, QString(), QString(), conditions, QString()).onResult([this]() {
|
||||
connection()->callApi<SetPushRuleEnabledJob>("override"_L1, id(), true).onResult([this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
});
|
||||
@@ -1131,10 +1136,8 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
// No conditions for a room rule
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("room"_L1, id(), actions, QString(), QString(), conditions, QString());
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("room"_L1, id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
connection()->callApi<SetPushRuleJob>("room"_L1, id(), actions, QString(), QString(), conditions, QString()).onResult([this]() {
|
||||
connection()->callApi<SetPushRuleEnabledJob>("room"_L1, id(), true).onResult([this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
});
|
||||
@@ -1162,10 +1165,8 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
const QList<PushCondition> conditions;
|
||||
|
||||
// Add new room rule and make sure enabled
|
||||
auto setJob = connection()->callApi<SetPushRuleJob>("room"_L1, id(), actions, QString(), QString(), conditions, QString());
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = connection()->callApi<SetPushRuleEnabledJob>("room"_L1, id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
connection()->callApi<SetPushRuleJob>("room"_L1, id(), actions, QString(), QString(), conditions, QString()).onResult([this]() {
|
||||
connection()->callApi<SetPushRuleEnabledJob>("room"_L1, id(), true).onResult([this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
});
|
||||
@@ -1232,20 +1233,15 @@ void NeoChatRoom::updatePushNotificationState(QString type)
|
||||
|
||||
void NeoChatRoom::reportEvent(const QString &eventId, const QString &reason)
|
||||
{
|
||||
auto job = connection()->callApi<ReportContentJob>(id(), eventId, -50, reason);
|
||||
connect(job, &BaseJob::finished, this, [this, job]() {
|
||||
if (job->error() == BaseJob::Success) {
|
||||
Q_EMIT showMessage(MessageType::Positive, i18n("Report sent successfully."));
|
||||
}
|
||||
auto job = connection()->callApi<ReportContentJob>(id(), eventId, -50, reason).onResult([this]() {
|
||||
Q_EMIT showMessage(MessageType::Positive, i18n("Report sent successfully."));
|
||||
});
|
||||
}
|
||||
|
||||
QByteArray NeoChatRoom::getEventJsonSource(const QString &eventId)
|
||||
{
|
||||
auto evtIt = findInTimeline(eventId);
|
||||
if (evtIt != messageEvents().rend() && is<RoomEvent>(**evtIt)) {
|
||||
const auto event = evtIt->viewAs<RoomEvent>();
|
||||
return QJsonDocument(event->fullJson()).toJson();
|
||||
if (const auto evtIt = findInTimeline(eventId); evtIt != messageEvents().rend() && is<RoomEvent>(**evtIt)) {
|
||||
return QJsonDocument(evtIt->viewAs<RoomEvent>()->fullJson()).toJson();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -1533,13 +1529,11 @@ void NeoChatRoom::download(const QString &eventId, const QUrl &localFilename)
|
||||
|
||||
void NeoChatRoom::mapAlias(const QString &alias)
|
||||
{
|
||||
auto getLocalAliasesJob = connection()->callApi<GetLocalAliasesJob>(id());
|
||||
connect(getLocalAliasesJob, &BaseJob::success, this, [this, getLocalAliasesJob, alias] {
|
||||
if (getLocalAliasesJob->aliases().contains(alias)) {
|
||||
connection()->callApi<GetLocalAliasesJob>(id()).onResult([this, alias](const auto &job) {
|
||||
if (job->aliases().contains(alias)) {
|
||||
return;
|
||||
} else {
|
||||
auto setRoomAliasJob = connection()->callApi<SetRoomAliasJob>(alias, id());
|
||||
connect(setRoomAliasJob, &BaseJob::success, this, [this, alias] {
|
||||
connection()->callApi<SetRoomAliasJob>(alias, id()).onResult([this, alias] {
|
||||
auto newAltAliases = altAliases();
|
||||
newAltAliases.append(alias);
|
||||
setLocalAliases(newAltAliases);
|
||||
@@ -1631,23 +1625,25 @@ void NeoChatRoom::downloadEventFromServer(const QString &eventId)
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
return;
|
||||
}
|
||||
auto job = connection()->callApi<GetOneRoomEventJob>(id(), eventId);
|
||||
connect(job, &BaseJob::success, this, [this, job, eventId] {
|
||||
// The event may have arrived in the meantime so check it's not in the timeline.
|
||||
if (findInTimeline(eventId) != historyEdge()) {
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
return;
|
||||
}
|
||||
connection()
|
||||
->callApi<GetOneRoomEventJob>(id(), eventId)
|
||||
.then(
|
||||
[this, eventId](const auto &job) {
|
||||
// The event may have arrived in the meantime so check it's not in the timeline.
|
||||
if (findInTimeline(eventId) != historyEdge()) {
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
return;
|
||||
}
|
||||
|
||||
event_ptr_tt<RoomEvent> event = fromJson<event_ptr_tt<RoomEvent>>(job->jsonData());
|
||||
m_extraEvents.push_back(std::move(event));
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
});
|
||||
connect(job, &BaseJob::failure, this, [this, job, eventId] {
|
||||
if (job->error() == BaseJob::NotFound) {
|
||||
Q_EMIT extraEventNotFound(eventId);
|
||||
}
|
||||
});
|
||||
event_ptr_tt<RoomEvent> event = fromJson<event_ptr_tt<RoomEvent>>(job->jsonData());
|
||||
m_extraEvents.push_back(std::move(event));
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
},
|
||||
[this, eventId](const auto &job) {
|
||||
if (job->error() == BaseJob::NotFound) {
|
||||
Q_EMIT extraEventNotFound(eventId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::pair<const Quotient::RoomEvent *, bool> NeoChatRoom::getEvent(const QString &eventId) const
|
||||
|
||||
@@ -112,7 +112,7 @@ Kirigami.ScrollablePage {
|
||||
* @brief Force the search to be updated if the model has a valid search function.
|
||||
*/
|
||||
function updateSearch() {
|
||||
searchTimer.restart();
|
||||
root.model.search();
|
||||
}
|
||||
|
||||
header: QQC2.Control {
|
||||
@@ -142,10 +142,13 @@ Kirigami.ScrollablePage {
|
||||
Layout.fillWidth: true
|
||||
Keys.onEnterPressed: searchButton.clicked()
|
||||
Keys.onReturnPressed: searchButton.clicked()
|
||||
onTextChanged: {
|
||||
searchTimer.restart();
|
||||
if (root.model) {
|
||||
root.model.searchText = text;
|
||||
onTextChanged: root.model.searchText = text
|
||||
onAccepted: {
|
||||
// If the text is empty, call the search model immediately because it will early-return.
|
||||
if (root.model.searchText.length === 0) {
|
||||
root.model.search();
|
||||
} else {
|
||||
searchTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,6 +161,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
onClicked: {
|
||||
if (typeof root.model.search === 'function') {
|
||||
searchTimer.stop();
|
||||
root.model.search();
|
||||
}
|
||||
}
|
||||
@@ -187,6 +191,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: noSearchMessage
|
||||
icon.name: "search"
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length === 0 && listView.count === 0 && customPlaceholder.text.length === 0
|
||||
helpfulAction: root.noSearchHelpfulAction
|
||||
@@ -194,8 +199,9 @@ Kirigami.ScrollablePage {
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: noResultMessage
|
||||
icon.name: "search"
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && customPlaceholder.text.length === 0
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && (!root.model.searching && !searchTimer.running) && customPlaceholder.text.length === 0
|
||||
helpfulAction: root.noResultHelpfulAction
|
||||
}
|
||||
|
||||
@@ -208,7 +214,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
Kirigami.LoadingPlaceholder {
|
||||
anchors.centerIn: parent
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && root.model.searching && customPlaceholder.text.length === 0
|
||||
visible: searchField.text.length > 0 && listView.count === 0 && (root.model.searching || searchTimer.running) && customPlaceholder.text.length === 0
|
||||
}
|
||||
|
||||
Keys.onUpPressed: {
|
||||
|
||||
@@ -61,13 +61,12 @@ void SpaceHierarchyCache::populateSpaceHierarchy(const QString &spaceId)
|
||||
}
|
||||
|
||||
m_nextBatchTokens[spaceId] = QString();
|
||||
auto job = m_connection->callApi<GetSpaceHierarchyJob>(spaceId, std::nullopt, std::nullopt, std::nullopt, *m_nextBatchTokens[spaceId]);
|
||||
m_connection->callApi<GetSpaceHierarchyJob>(spaceId, std::nullopt, std::nullopt, std::nullopt, *m_nextBatchTokens[spaceId])
|
||||
.onResult([this, spaceId](const auto &job) {
|
||||
addBatch(spaceId, job);
|
||||
});
|
||||
auto group = KConfigGroup(KSharedConfig::openStateConfig("SpaceHierarchy"_L1), "Cache"_L1);
|
||||
m_spaceHierarchy.insert(spaceId, group.readEntry(spaceId, QStringList()));
|
||||
|
||||
connect(job, &BaseJob::success, this, [this, job, spaceId]() {
|
||||
addBatch(spaceId, job);
|
||||
});
|
||||
}
|
||||
|
||||
void SpaceHierarchyCache::addBatch(const QString &spaceId, Quotient::GetSpaceHierarchyJob *job)
|
||||
@@ -90,10 +89,10 @@ void SpaceHierarchyCache::addBatch(const QString &spaceId, Quotient::GetSpaceHie
|
||||
const auto nextBatchToken = job->nextBatch();
|
||||
if (!nextBatchToken.isEmpty() && nextBatchToken != *m_nextBatchTokens[spaceId] && m_connection) {
|
||||
*m_nextBatchTokens[spaceId] = nextBatchToken;
|
||||
auto nextJob = m_connection->callApi<GetSpaceHierarchyJob>(spaceId, std::nullopt, std::nullopt, std::nullopt, *m_nextBatchTokens[spaceId]);
|
||||
connect(nextJob, &BaseJob::success, this, [this, nextJob, spaceId]() {
|
||||
addBatch(spaceId, nextJob);
|
||||
});
|
||||
m_connection->callApi<GetSpaceHierarchyJob>(spaceId, std::nullopt, std::nullopt, std::nullopt, *m_nextBatchTokens[spaceId])
|
||||
.onResult([this, spaceId](const auto &nextJob) {
|
||||
addBatch(spaceId, nextJob);
|
||||
});
|
||||
} else {
|
||||
m_nextBatchTokens[spaceId].reset();
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ RowLayout {
|
||||
textFormat: Text.PlainText
|
||||
font.weight: Font.Bold
|
||||
elide: Text.ElideRight
|
||||
clip: true // Intentional to limit insane Unicode in display names
|
||||
|
||||
function openUserMenu(): void {
|
||||
const menu = Qt.createComponent("org.kde.neochat", "UserMenu").createObject(root, {
|
||||
|
||||
@@ -26,6 +26,7 @@ QQC2.Control {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Message.maxContentWidth
|
||||
implicitWidth: Message.maxContentWidth
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
Loader {
|
||||
|
||||
@@ -129,7 +129,7 @@ QQC2.Control {
|
||||
|
||||
TapHandler {
|
||||
acceptedDevices: PointerDevice.TouchScreen
|
||||
onLongPressed: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.author, root.Message.selectedText, root.Message.hoveredLink);
|
||||
onLongPressed: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.Message.selectedText, root.Message.hoveredLink);
|
||||
}
|
||||
|
||||
background: null
|
||||
|
||||
@@ -27,6 +27,15 @@ ColumnLayout {
|
||||
*/
|
||||
required property string display
|
||||
|
||||
/**
|
||||
* @brief The attributes of the component.
|
||||
*/
|
||||
required property var componentAttributes
|
||||
|
||||
required property int index
|
||||
|
||||
required property NeochatRoomMember author
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Message.maxContentWidth
|
||||
|
||||
@@ -83,5 +92,9 @@ ColumnLayout {
|
||||
TextComponent {
|
||||
display: root.display
|
||||
visible: root.display !== ""
|
||||
componentAttributes: root.componentAttributes
|
||||
index: root.index
|
||||
eventId: root.eventId
|
||||
author: root.author
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ RowLayout {
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Message.maxContentWidth
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
spacing: 0
|
||||
|
||||
QQC2.BusyIndicator {}
|
||||
Kirigami.Heading {
|
||||
@@ -27,7 +27,7 @@ RowLayout {
|
||||
Layout.fillWidth: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
level: 2
|
||||
text: root.display.length > 0 ? root.display : i18n("Loading")
|
||||
text: root.display.length > 0 ? root.display : i18nc("@info Loading this message", "Loading…")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ ColumnLayout {
|
||||
*/
|
||||
required property var componentAttributes
|
||||
|
||||
required property int index
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: Message.maxContentWidth
|
||||
|
||||
@@ -124,5 +126,7 @@ ColumnLayout {
|
||||
author: root.author
|
||||
display: root.display
|
||||
visible: root.display !== ""
|
||||
index: root.index
|
||||
componentAttributes: root.componentAttributes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ QQC2.Control {
|
||||
textFormat: TextEdit.RichText
|
||||
wrapMode: TextEdit.Wrap
|
||||
color: Kirigami.Theme.textColor
|
||||
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
|
||||
font.italic: true
|
||||
|
||||
@@ -63,7 +65,7 @@ QQC2.Control {
|
||||
enabled: !quoteText.hoveredLink
|
||||
acceptedDevices: PointerDevice.TouchScreen
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onLongPressed: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.author, root.Message.selectedText, root.Message.hoveredLink);
|
||||
onLongPressed: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.Message.selectedText, root.Message.hoveredLink);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,12 +95,12 @@ TextEdit {
|
||||
enabled: !root.hoveredLink
|
||||
acceptedButtons: Qt.LeftButton
|
||||
acceptedDevices: PointerDevice.TouchScreen
|
||||
onLongPressed: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.author, root.Message.selectedText, root.Message.hoveredLink);
|
||||
onLongPressed: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.Message.selectedText, root.Message.hoveredLink);
|
||||
}
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.RightButton
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
|
||||
gesturePolicy: TapHandler.WithinBounds
|
||||
onTapped: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.author, root.Message.selectedText, root.Message.hoveredLink);
|
||||
onTapped: RoomManager.viewEventMenu(root.eventId, root.Message.room, root.Message.selectedText, root.Message.hoveredLink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,8 @@ void EventMessageContentModel::resetModel()
|
||||
|
||||
const auto event = m_room->getEvent(m_eventId);
|
||||
if (event.first == nullptr) {
|
||||
m_components += MessageComponent{MessageComponentType::Loading, m_isReply ? i18n("Loading reply") : i18n("Loading"), {}};
|
||||
m_components +=
|
||||
MessageComponent{MessageComponentType::Loading, m_isReply ? i18nc("@info", "Loading reply…") : i18nc("@info Loading this message", "Loading…"), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
@@ -448,7 +449,7 @@ QList<MessageComponent> EventMessageContentModel::componentsForType(MessageCompo
|
||||
}
|
||||
case MessageComponentType::Location:
|
||||
return {MessageComponent{type,
|
||||
QString(),
|
||||
EventHandler::plainBody(m_room, event.first),
|
||||
{
|
||||
{u"latitude"_s, EventHandler::latitude(event.first)},
|
||||
{u"longitude"_s, EventHandler::longitude(event.first)},
|
||||
|
||||
@@ -259,7 +259,7 @@ MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
|
||||
it->type = MessageComponentType::LinkPreview;
|
||||
Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {ComponentTypeRole});
|
||||
}
|
||||
return it;
|
||||
return ++it;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user